tree_sitter_index.rs

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