syntax_index.rs

  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}