1use std::sync::Arc;
2
3use collections::{HashMap, HashSet};
4use futures::lock::Mutex;
5use gpui::{App, AppContext as _, Context, Entity, Task, WeakEntity};
6use language::{Buffer, BufferEvent};
7use project::buffer_store::{BufferStore, BufferStoreEvent};
8use project::worktree_store::{WorktreeStore, WorktreeStoreEvent};
9use project::{PathChange, Project, ProjectEntryId, ProjectPath};
10use slotmap::SlotMap;
11use text::BufferId;
12use util::{ResultExt as _, debug_panic, some_or_debug_panic};
13
14use crate::declaration::{
15 BufferDeclaration, Declaration, DeclarationId, FileDeclaration, Identifier,
16};
17use crate::outline::declarations_in_buffer;
18
19// TODO:
20//
21// * Skip for remote projects
22//
23// * Consider making SyntaxIndex not an Entity.
24
25// Potential future improvements:
26//
27// * Send multiple selected excerpt ranges. Challenge is that excerpt ranges influence which
28// references are present and their scores.
29
30// Potential future optimizations:
31//
32// * Cache of buffers for files
33//
34// * Parse files directly instead of loading into a Rope. Make SyntaxMap generic to handle embedded
35// languages? Will also need to find line boundaries, but that can be done by scanning characters in
36// the flat representation.
37//
38// * Use something similar to slotmap without key versions.
39//
40// * Concurrent slotmap
41//
42// * Use queue for parsing
43//
44
45pub struct SyntaxIndex {
46 state: Arc<Mutex<SyntaxIndexState>>,
47 project: WeakEntity<Project>,
48}
49
50#[derive(Default)]
51pub struct SyntaxIndexState {
52 declarations: SlotMap<DeclarationId, Declaration>,
53 identifiers: HashMap<Identifier, HashSet<DeclarationId>>,
54 files: HashMap<ProjectEntryId, FileState>,
55 buffers: HashMap<BufferId, BufferState>,
56}
57
58#[derive(Debug, Default)]
59struct FileState {
60 declarations: Vec<DeclarationId>,
61 task: Option<Task<()>>,
62}
63
64#[derive(Default)]
65struct BufferState {
66 declarations: Vec<DeclarationId>,
67 task: Option<Task<()>>,
68}
69
70impl SyntaxIndex {
71 pub fn new(project: &Entity<Project>, cx: &mut Context<Self>) -> Self {
72 let mut this = Self {
73 project: project.downgrade(),
74 state: Arc::new(Mutex::new(SyntaxIndexState::default())),
75 };
76
77 let worktree_store = project.read(cx).worktree_store();
78 cx.subscribe(&worktree_store, Self::handle_worktree_store_event)
79 .detach();
80
81 for worktree in worktree_store
82 .read(cx)
83 .worktrees()
84 .map(|w| w.read(cx).snapshot())
85 .collect::<Vec<_>>()
86 {
87 for entry in worktree.files(false, 0) {
88 this.update_file(
89 entry.id,
90 ProjectPath {
91 worktree_id: worktree.id(),
92 path: entry.path.clone(),
93 },
94 cx,
95 );
96 }
97 }
98
99 let buffer_store = project.read(cx).buffer_store().clone();
100 for buffer in buffer_store.read(cx).buffers().collect::<Vec<_>>() {
101 this.register_buffer(&buffer, cx);
102 }
103 cx.subscribe(&buffer_store, Self::handle_buffer_store_event)
104 .detach();
105
106 this
107 }
108
109 fn handle_worktree_store_event(
110 &mut self,
111 _worktree_store: Entity<WorktreeStore>,
112 event: &WorktreeStoreEvent,
113 cx: &mut Context<Self>,
114 ) {
115 use WorktreeStoreEvent::*;
116 match event {
117 WorktreeUpdatedEntries(worktree_id, updated_entries_set) => {
118 let state = Arc::downgrade(&self.state);
119 let worktree_id = *worktree_id;
120 let updated_entries_set = updated_entries_set.clone();
121 cx.spawn(async move |this, cx| {
122 let Some(state) = state.upgrade() else { return };
123 for (path, entry_id, path_change) in updated_entries_set.iter() {
124 if let PathChange::Removed = path_change {
125 state.lock().await.files.remove(entry_id);
126 } else {
127 let project_path = ProjectPath {
128 worktree_id,
129 path: path.clone(),
130 };
131 this.update(cx, |this, cx| {
132 this.update_file(*entry_id, project_path, cx);
133 })
134 .ok();
135 }
136 }
137 })
138 .detach();
139 }
140 WorktreeDeletedEntry(_worktree_id, project_entry_id) => {
141 let project_entry_id = *project_entry_id;
142 self.with_state(cx, move |state| {
143 state.files.remove(&project_entry_id);
144 })
145 }
146 _ => {}
147 }
148 }
149
150 fn handle_buffer_store_event(
151 &mut self,
152 _buffer_store: Entity<BufferStore>,
153 event: &BufferStoreEvent,
154 cx: &mut Context<Self>,
155 ) {
156 use BufferStoreEvent::*;
157 match event {
158 BufferAdded(buffer) => self.register_buffer(buffer, cx),
159 BufferOpened { .. }
160 | BufferChangedFilePath { .. }
161 | BufferDropped { .. }
162 | SharedBufferClosed { .. } => {}
163 }
164 }
165
166 pub fn state(&self) -> &Arc<Mutex<SyntaxIndexState>> {
167 &self.state
168 }
169
170 fn with_state(&self, cx: &mut App, f: impl FnOnce(&mut SyntaxIndexState) + Send + 'static) {
171 if let Some(mut state) = self.state.try_lock() {
172 f(&mut state);
173 return;
174 }
175 let state = Arc::downgrade(&self.state);
176 cx.background_spawn(async move {
177 let Some(state) = state.upgrade() else {
178 return None;
179 };
180 let mut state = state.lock().await;
181 Some(f(&mut state))
182 })
183 .detach();
184 }
185
186 fn register_buffer(&mut self, buffer: &Entity<Buffer>, cx: &mut Context<Self>) {
187 let buffer_id = buffer.read(cx).remote_id();
188 cx.observe_release(buffer, move |this, _buffer, cx| {
189 this.with_state(cx, move |state| {
190 if let Some(buffer_state) = state.buffers.remove(&buffer_id) {
191 SyntaxIndexState::remove_buffer_declarations(
192 &buffer_state.declarations,
193 &mut state.declarations,
194 &mut state.identifiers,
195 );
196 }
197 })
198 })
199 .detach();
200 cx.subscribe(buffer, Self::handle_buffer_event).detach();
201
202 self.update_buffer(buffer.clone(), cx);
203 }
204
205 fn handle_buffer_event(
206 &mut self,
207 buffer: Entity<Buffer>,
208 event: &BufferEvent,
209 cx: &mut Context<Self>,
210 ) {
211 match event {
212 BufferEvent::Edited => self.update_buffer(buffer, cx),
213 _ => {}
214 }
215 }
216
217 fn update_buffer(&mut self, buffer_entity: Entity<Buffer>, cx: &mut Context<Self>) {
218 let buffer = buffer_entity.read(cx);
219
220 let Some(project_entry_id) =
221 project::File::from_dyn(buffer.file()).and_then(|f| f.project_entry_id(cx))
222 else {
223 return;
224 };
225 let buffer_id = buffer.remote_id();
226
227 let mut parse_status = buffer.parse_status();
228 let snapshot_task = cx.spawn({
229 let weak_buffer = buffer_entity.downgrade();
230 async move |_, cx| {
231 while *parse_status.borrow() != language::ParseStatus::Idle {
232 parse_status.changed().await?;
233 }
234 weak_buffer.read_with(cx, |buffer, _cx| buffer.snapshot())
235 }
236 });
237
238 let parse_task = cx.background_spawn(async move {
239 let snapshot = snapshot_task.await?;
240 let rope = snapshot.text.as_rope().clone();
241
242 anyhow::Ok((
243 rope,
244 declarations_in_buffer(&snapshot)
245 .into_iter()
246 .map(|item| (item.parent_index, BufferDeclaration::from_outline(item)))
247 .collect::<Vec<_>>(),
248 ))
249 });
250
251 let task = cx.spawn({
252 async move |this, cx| {
253 let Ok((rope, declarations)) = parse_task.await else {
254 return;
255 };
256
257 this.update(cx, move |this, cx| {
258 this.with_state(cx, move |state| {
259 let buffer_state = state
260 .buffers
261 .entry(buffer_id)
262 .or_insert_with(Default::default);
263
264 SyntaxIndexState::remove_buffer_declarations(
265 &buffer_state.declarations,
266 &mut state.declarations,
267 &mut state.identifiers,
268 );
269
270 let mut new_ids = Vec::with_capacity(declarations.len());
271 state.declarations.reserve(declarations.len());
272 for (parent_index, mut declaration) in declarations {
273 declaration.parent = parent_index
274 .and_then(|ix| some_or_debug_panic(new_ids.get(ix).copied()));
275
276 let identifier = declaration.identifier.clone();
277 let declaration_id = state.declarations.insert(Declaration::Buffer {
278 rope: rope.clone(),
279 buffer_id,
280 declaration,
281 project_entry_id,
282 });
283 new_ids.push(declaration_id);
284
285 state
286 .identifiers
287 .entry(identifier)
288 .or_default()
289 .insert(declaration_id);
290 }
291
292 buffer_state.declarations = new_ids;
293 });
294 })
295 .ok();
296 }
297 });
298
299 self.with_state(cx, move |state| {
300 state
301 .buffers
302 .entry(buffer_id)
303 .or_insert_with(Default::default)
304 .task = Some(task)
305 });
306 }
307
308 fn update_file(
309 &mut self,
310 entry_id: ProjectEntryId,
311 project_path: ProjectPath,
312 cx: &mut Context<Self>,
313 ) {
314 let Some(project) = self.project.upgrade() else {
315 return;
316 };
317 let project = project.read(cx);
318 let Some(worktree) = project.worktree_for_id(project_path.worktree_id, cx) else {
319 return;
320 };
321 let language_registry = project.languages().clone();
322
323 let snapshot_task = worktree.update(cx, |worktree, cx| {
324 let load_task = worktree.load_file(&project_path.path, cx);
325 cx.spawn(async move |_this, cx| {
326 let loaded_file = load_task.await?;
327 let language = language_registry
328 .language_for_file_path(&project_path.path)
329 .await
330 .log_err();
331
332 let buffer = cx.new(|cx| {
333 let mut buffer = Buffer::local(loaded_file.text, cx);
334 buffer.set_language(language, cx);
335 buffer
336 })?;
337
338 let mut parse_status = buffer.read_with(cx, |buffer, _| buffer.parse_status())?;
339 while *parse_status.borrow() != language::ParseStatus::Idle {
340 parse_status.changed().await?;
341 }
342
343 buffer.read_with(cx, |buffer, _cx| buffer.snapshot())
344 })
345 });
346
347 let parse_task = cx.background_spawn(async move {
348 let snapshot = snapshot_task.await?;
349 let rope = snapshot.as_rope();
350 let declarations = declarations_in_buffer(&snapshot)
351 .into_iter()
352 .map(|item| (item.parent_index, FileDeclaration::from_outline(item, rope)))
353 .collect::<Vec<_>>();
354 anyhow::Ok(declarations)
355 });
356
357 let task = cx.spawn({
358 async move |this, cx| {
359 // TODO: how to handle errors?
360 let Ok(declarations) = parse_task.await else {
361 return;
362 };
363 this.update(cx, |this, cx| {
364 this.with_state(cx, move |state| {
365 let file_state =
366 state.files.entry(entry_id).or_insert_with(Default::default);
367
368 for old_declaration_id in &file_state.declarations {
369 let Some(declaration) = state.declarations.remove(*old_declaration_id)
370 else {
371 debug_panic!("declaration not found");
372 continue;
373 };
374 if let Some(identifier_declarations) =
375 state.identifiers.get_mut(declaration.identifier())
376 {
377 identifier_declarations.remove(old_declaration_id);
378 }
379 }
380
381 let mut new_ids = Vec::with_capacity(declarations.len());
382 state.declarations.reserve(declarations.len());
383
384 for (parent_index, mut declaration) in declarations {
385 declaration.parent = parent_index
386 .and_then(|ix| some_or_debug_panic(new_ids.get(ix).copied()));
387
388 let identifier = declaration.identifier.clone();
389 let declaration_id = state.declarations.insert(Declaration::File {
390 project_entry_id: entry_id,
391 declaration,
392 });
393 new_ids.push(declaration_id);
394
395 state
396 .identifiers
397 .entry(identifier)
398 .or_default()
399 .insert(declaration_id);
400 }
401
402 file_state.declarations = new_ids;
403 });
404 })
405 .ok();
406 }
407 });
408
409 self.with_state(cx, move |state| {
410 state
411 .files
412 .entry(entry_id)
413 .or_insert_with(Default::default)
414 .task = Some(task);
415 });
416 }
417}
418
419impl SyntaxIndexState {
420 pub fn declaration(&self, id: DeclarationId) -> Option<&Declaration> {
421 self.declarations.get(id)
422 }
423
424 pub fn declarations_for_identifier<const N: usize>(
425 &self,
426 identifier: &Identifier,
427 ) -> Vec<Declaration> {
428 // make sure to not have a large stack allocation
429 assert!(N < 32);
430
431 let Some(declaration_ids) = self.identifiers.get(&identifier) else {
432 return vec![];
433 };
434
435 let mut result = Vec::with_capacity(N);
436 let mut included_buffer_entry_ids = arrayvec::ArrayVec::<_, N>::new();
437 let mut file_declarations = Vec::new();
438
439 for declaration_id in declaration_ids {
440 let declaration = self.declarations.get(*declaration_id);
441 let Some(declaration) = some_or_debug_panic(declaration) else {
442 continue;
443 };
444 match declaration {
445 Declaration::Buffer {
446 project_entry_id, ..
447 } => {
448 included_buffer_entry_ids.push(*project_entry_id);
449 result.push(declaration.clone());
450 if result.len() == N {
451 return result;
452 }
453 }
454 Declaration::File {
455 project_entry_id, ..
456 } => {
457 if !included_buffer_entry_ids.contains(&project_entry_id) {
458 file_declarations.push(declaration.clone());
459 }
460 }
461 }
462 }
463
464 for declaration in file_declarations {
465 match declaration {
466 Declaration::File {
467 project_entry_id, ..
468 } => {
469 if !included_buffer_entry_ids.contains(&project_entry_id) {
470 result.push(declaration);
471
472 if result.len() == N {
473 return result;
474 }
475 }
476 }
477 Declaration::Buffer { .. } => {}
478 }
479 }
480
481 result
482 }
483
484 pub fn file_declaration_count(&self, declaration: &Declaration) -> usize {
485 match declaration {
486 Declaration::File {
487 project_entry_id, ..
488 } => self
489 .files
490 .get(project_entry_id)
491 .map(|file_state| file_state.declarations.len())
492 .unwrap_or_default(),
493 Declaration::Buffer { buffer_id, .. } => self
494 .buffers
495 .get(buffer_id)
496 .map(|buffer_state| buffer_state.declarations.len())
497 .unwrap_or_default(),
498 }
499 }
500
501 fn remove_buffer_declarations(
502 old_declaration_ids: &[DeclarationId],
503 declarations: &mut SlotMap<DeclarationId, Declaration>,
504 identifiers: &mut HashMap<Identifier, HashSet<DeclarationId>>,
505 ) {
506 for old_declaration_id in old_declaration_ids {
507 let Some(declaration) = declarations.remove(*old_declaration_id) else {
508 debug_panic!("declaration not found");
509 continue;
510 };
511 if let Some(identifier_declarations) = identifiers.get_mut(declaration.identifier()) {
512 identifier_declarations.remove(old_declaration_id);
513 }
514 }
515 }
516}
517
518#[cfg(test)]
519mod tests {
520 use super::*;
521 use std::{path::Path, sync::Arc};
522
523 use gpui::TestAppContext;
524 use indoc::indoc;
525 use language::{Language, LanguageConfig, LanguageId, LanguageMatcher, tree_sitter_rust};
526 use project::{FakeFs, Project};
527 use serde_json::json;
528 use settings::SettingsStore;
529 use text::OffsetRangeExt as _;
530 use util::path;
531
532 use crate::syntax_index::SyntaxIndex;
533
534 #[gpui::test]
535 async fn test_unopen_indexed_files(cx: &mut TestAppContext) {
536 let (project, index, rust_lang_id) = init_test(cx).await;
537 let main = Identifier {
538 name: "main".into(),
539 language_id: rust_lang_id,
540 };
541
542 let index_state = index.read_with(cx, |index, _cx| index.state().clone());
543 let index_state = index_state.lock().await;
544 cx.update(|cx| {
545 let decls = index_state.declarations_for_identifier::<8>(&main);
546 assert_eq!(decls.len(), 2);
547
548 let decl = expect_file_decl("c.rs", &decls[0], &project, cx);
549 assert_eq!(decl.identifier, main.clone());
550 assert_eq!(decl.item_range_in_file, 32..280);
551
552 let decl = expect_file_decl("a.rs", &decls[1], &project, cx);
553 assert_eq!(decl.identifier, main);
554 assert_eq!(decl.item_range_in_file, 0..98);
555 });
556 }
557
558 #[gpui::test]
559 async fn test_parents_in_file(cx: &mut TestAppContext) {
560 let (project, index, rust_lang_id) = init_test(cx).await;
561 let test_process_data = Identifier {
562 name: "test_process_data".into(),
563 language_id: rust_lang_id,
564 };
565
566 let index_state = index.read_with(cx, |index, _cx| index.state().clone());
567 let index_state = index_state.lock().await;
568 cx.update(|cx| {
569 let decls = index_state.declarations_for_identifier::<8>(&test_process_data);
570 assert_eq!(decls.len(), 1);
571
572 let decl = expect_file_decl("c.rs", &decls[0], &project, cx);
573 assert_eq!(decl.identifier, test_process_data);
574
575 let parent_id = decl.parent.unwrap();
576 let parent = index_state.declaration(parent_id).unwrap();
577 let parent_decl = expect_file_decl("c.rs", &parent, &project, cx);
578 assert_eq!(
579 parent_decl.identifier,
580 Identifier {
581 name: "tests".into(),
582 language_id: rust_lang_id
583 }
584 );
585 assert_eq!(parent_decl.parent, None);
586 });
587 }
588
589 #[gpui::test]
590 async fn test_parents_in_buffer(cx: &mut TestAppContext) {
591 let (project, index, rust_lang_id) = init_test(cx).await;
592 let test_process_data = Identifier {
593 name: "test_process_data".into(),
594 language_id: rust_lang_id,
595 };
596
597 let buffer = project
598 .update(cx, |project, cx| {
599 let project_path = project.find_project_path("c.rs", cx).unwrap();
600 project.open_buffer(project_path, cx)
601 })
602 .await
603 .unwrap();
604
605 cx.run_until_parked();
606
607 let index_state = index.read_with(cx, |index, _cx| index.state().clone());
608 let index_state = index_state.lock().await;
609 cx.update(|cx| {
610 let decls = index_state.declarations_for_identifier::<8>(&test_process_data);
611 assert_eq!(decls.len(), 1);
612
613 let decl = expect_buffer_decl("c.rs", &decls[0], &project, cx);
614 assert_eq!(decl.identifier, test_process_data);
615
616 let parent_id = decl.parent.unwrap();
617 let parent = index_state.declaration(parent_id).unwrap();
618 let parent_decl = expect_buffer_decl("c.rs", &parent, &project, cx);
619 assert_eq!(
620 parent_decl.identifier,
621 Identifier {
622 name: "tests".into(),
623 language_id: rust_lang_id
624 }
625 );
626 assert_eq!(parent_decl.parent, None);
627 });
628
629 drop(buffer);
630 }
631
632 #[gpui::test]
633 async fn test_declarations_limt(cx: &mut TestAppContext) {
634 let (_, index, rust_lang_id) = init_test(cx).await;
635
636 let index_state = index.read_with(cx, |index, _cx| index.state().clone());
637 let index_state = index_state.lock().await;
638 let decls = index_state.declarations_for_identifier::<1>(&Identifier {
639 name: "main".into(),
640 language_id: rust_lang_id,
641 });
642 assert_eq!(decls.len(), 1);
643 }
644
645 #[gpui::test]
646 async fn test_buffer_shadow(cx: &mut TestAppContext) {
647 let (project, index, rust_lang_id) = init_test(cx).await;
648
649 let main = Identifier {
650 name: "main".into(),
651 language_id: rust_lang_id,
652 };
653
654 let buffer = project
655 .update(cx, |project, cx| {
656 let project_path = project.find_project_path("c.rs", cx).unwrap();
657 project.open_buffer(project_path, cx)
658 })
659 .await
660 .unwrap();
661
662 cx.run_until_parked();
663
664 let index_state_arc = index.read_with(cx, |index, _cx| index.state().clone());
665 {
666 let index_state = index_state_arc.lock().await;
667
668 cx.update(|cx| {
669 let decls = index_state.declarations_for_identifier::<8>(&main);
670 assert_eq!(decls.len(), 2);
671 let decl = expect_buffer_decl("c.rs", &decls[0], &project, cx);
672 assert_eq!(decl.identifier, main);
673 assert_eq!(decl.item_range.to_offset(&buffer.read(cx)), 32..279);
674
675 expect_file_decl("a.rs", &decls[1], &project, cx);
676 });
677 }
678
679 // Drop the buffer and wait for release
680 cx.update(|_| {
681 drop(buffer);
682 });
683 cx.run_until_parked();
684
685 let index_state = index_state_arc.lock().await;
686
687 cx.update(|cx| {
688 let decls = index_state.declarations_for_identifier::<8>(&main);
689 assert_eq!(decls.len(), 2);
690 expect_file_decl("c.rs", &decls[0], &project, cx);
691 expect_file_decl("a.rs", &decls[1], &project, cx);
692 });
693 }
694
695 fn expect_buffer_decl<'a>(
696 path: &str,
697 declaration: &'a Declaration,
698 project: &Entity<Project>,
699 cx: &App,
700 ) -> &'a BufferDeclaration {
701 if let Declaration::Buffer {
702 declaration,
703 project_entry_id,
704 ..
705 } = declaration
706 {
707 let project_path = project
708 .read(cx)
709 .path_for_entry(*project_entry_id, cx)
710 .unwrap();
711 assert_eq!(project_path.path.as_ref(), Path::new(path),);
712 declaration
713 } else {
714 panic!("Expected a buffer declaration, found {:?}", declaration);
715 }
716 }
717
718 fn expect_file_decl<'a>(
719 path: &str,
720 declaration: &'a Declaration,
721 project: &Entity<Project>,
722 cx: &App,
723 ) -> &'a FileDeclaration {
724 if let Declaration::File {
725 declaration,
726 project_entry_id: file,
727 } = declaration
728 {
729 assert_eq!(
730 project
731 .read(cx)
732 .path_for_entry(*file, cx)
733 .unwrap()
734 .path
735 .as_ref(),
736 Path::new(path),
737 );
738 declaration
739 } else {
740 panic!("Expected a file declaration, found {:?}", declaration);
741 }
742 }
743
744 async fn init_test(
745 cx: &mut TestAppContext,
746 ) -> (Entity<Project>, Entity<SyntaxIndex>, LanguageId) {
747 cx.update(|cx| {
748 let settings_store = SettingsStore::test(cx);
749 cx.set_global(settings_store);
750 language::init(cx);
751 Project::init_settings(cx);
752 });
753
754 let fs = FakeFs::new(cx.executor());
755 fs.insert_tree(
756 path!("/root"),
757 json!({
758 "a.rs": indoc! {r#"
759 fn main() {
760 let x = 1;
761 let y = 2;
762 let z = add(x, y);
763 println!("Result: {}", z);
764 }
765
766 fn add(a: i32, b: i32) -> i32 {
767 a + b
768 }
769 "#},
770 "b.rs": indoc! {"
771 pub struct Config {
772 pub name: String,
773 pub value: i32,
774 }
775
776 impl Config {
777 pub fn new(name: String, value: i32) -> Self {
778 Config { name, value }
779 }
780 }
781 "},
782 "c.rs": indoc! {r#"
783 use std::collections::HashMap;
784
785 fn main() {
786 let args: Vec<String> = std::env::args().collect();
787 let data: Vec<i32> = args[1..]
788 .iter()
789 .filter_map(|s| s.parse().ok())
790 .collect();
791 let result = process_data(data);
792 println!("{:?}", result);
793 }
794
795 fn process_data(data: Vec<i32>) -> HashMap<i32, usize> {
796 let mut counts = HashMap::new();
797 for value in data {
798 *counts.entry(value).or_insert(0) += 1;
799 }
800 counts
801 }
802
803 #[cfg(test)]
804 mod tests {
805 use super::*;
806
807 #[test]
808 fn test_process_data() {
809 let data = vec![1, 2, 2, 3];
810 let result = process_data(data);
811 assert_eq!(result.get(&2), Some(&2));
812 }
813 }
814 "#}
815 }),
816 )
817 .await;
818 let project = Project::test(fs.clone(), [path!("/root").as_ref()], cx).await;
819 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
820 let lang = rust_lang();
821 let lang_id = lang.id();
822 language_registry.add(Arc::new(lang));
823
824 let index = cx.new(|cx| SyntaxIndex::new(&project, cx));
825 cx.run_until_parked();
826
827 (project, index, lang_id)
828 }
829
830 fn rust_lang() -> Language {
831 Language::new(
832 LanguageConfig {
833 name: "Rust".into(),
834 matcher: LanguageMatcher {
835 path_suffixes: vec!["rs".to_string()],
836 ..Default::default()
837 },
838 ..Default::default()
839 },
840 Some(tree_sitter_rust::LANGUAGE.into()),
841 )
842 .with_outline_query(include_str!("../../languages/src/rust/outline.scm"))
843 .unwrap()
844 }
845}