1use ::util::rel_path::RelPath;
  2use ::util::{RangeExt, ResultExt as _};
  3use anyhow::{Context as _, Result};
  4use cloud_llm_client::predict_edits_v3::DeclarationScoreComponents;
  5use edit_prediction_context::{
  6    Declaration, DeclarationStyle, EditPredictionContext, Identifier, Imports, Reference,
  7    ReferenceRegion, SyntaxIndex, SyntaxIndexState, references_in_range,
  8};
  9use futures::StreamExt as _;
 10use futures::channel::mpsc;
 11use gpui::Entity;
 12use gpui::{AppContext, AsyncApp};
 13use language::OffsetRangeExt;
 14use language::{BufferSnapshot, Point};
 15use ordered_float::OrderedFloat;
 16use project::{Project, ProjectEntryId, ProjectPath, Worktree};
 17use serde::{Deserialize, Serialize};
 18use std::{
 19    cmp::Reverse,
 20    collections::{HashMap, HashSet},
 21    fs::File,
 22    hash::{Hash, Hasher},
 23    io::{BufRead, BufReader, BufWriter, Write as _},
 24    ops::Range,
 25    path::{Path, PathBuf},
 26    sync::{
 27        Arc,
 28        atomic::{self, AtomicUsize},
 29    },
 30    time::Duration,
 31};
 32use util::paths::PathStyle;
 33
 34use crate::headless::ZetaCliAppState;
 35use crate::source_location::SourceLocation;
 36use crate::util::{open_buffer, open_buffer_with_language_server};
 37
 38pub async fn retrieval_stats(
 39    worktree: PathBuf,
 40    app_state: Arc<ZetaCliAppState>,
 41    only_extension: Option<String>,
 42    file_limit: Option<usize>,
 43    skip_files: Option<usize>,
 44    options: zeta2::ZetaOptions,
 45    cx: &mut AsyncApp,
 46) -> Result<String> {
 47    let options = Arc::new(options);
 48    let worktree_path = worktree.canonicalize()?;
 49
 50    let project = cx.update(|cx| {
 51        Project::local(
 52            app_state.client.clone(),
 53            app_state.node_runtime.clone(),
 54            app_state.user_store.clone(),
 55            app_state.languages.clone(),
 56            app_state.fs.clone(),
 57            None,
 58            cx,
 59        )
 60    })?;
 61
 62    let worktree = project
 63        .update(cx, |project, cx| {
 64            project.create_worktree(&worktree_path, true, cx)
 65        })?
 66        .await?;
 67
 68    // wait for worktree scan so that wait_for_initial_file_indexing waits for the whole worktree.
 69    worktree
 70        .read_with(cx, |worktree, _cx| {
 71            worktree.as_local().unwrap().scan_complete()
 72        })?
 73        .await;
 74
 75    let index = cx.new(|cx| SyntaxIndex::new(&project, options.file_indexing_parallelism, cx))?;
 76    index
 77        .read_with(cx, |index, cx| index.wait_for_initial_file_indexing(cx))?
 78        .await?;
 79    let indexed_files = index
 80        .read_with(cx, |index, cx| index.indexed_file_paths(cx))?
 81        .await;
 82    let mut filtered_files = indexed_files
 83        .into_iter()
 84        .filter(|project_path| {
 85            let file_extension = project_path.path.extension();
 86            if let Some(only_extension) = only_extension.as_ref() {
 87                file_extension.is_some_and(|extension| extension == only_extension)
 88            } else {
 89                file_extension
 90                    .is_some_and(|extension| !["md", "json", "sh", "diff"].contains(&extension))
 91            }
 92        })
 93        .collect::<Vec<_>>();
 94    filtered_files.sort_by(|a, b| a.path.cmp(&b.path));
 95
 96    let index_state = index.read_with(cx, |index, _cx| index.state().clone())?;
 97    cx.update(|_| {
 98        drop(index);
 99    })?;
100    let index_state = Arc::new(
101        Arc::into_inner(index_state)
102            .context("Index state had more than 1 reference")?
103            .into_inner(),
104    );
105
106    struct FileSnapshot {
107        project_entry_id: ProjectEntryId,
108        snapshot: BufferSnapshot,
109        hash: u64,
110        parent_abs_path: Arc<Path>,
111    }
112
113    let files: Vec<FileSnapshot> = futures::future::try_join_all({
114        filtered_files
115            .iter()
116            .map(|file| {
117                let buffer_task =
118                    open_buffer(project.clone(), worktree.clone(), file.path.clone(), cx);
119                cx.spawn(async move |cx| {
120                    let buffer = buffer_task.await?;
121                    let (project_entry_id, parent_abs_path, snapshot) =
122                        buffer.read_with(cx, |buffer, cx| {
123                            let file = project::File::from_dyn(buffer.file()).unwrap();
124                            let project_entry_id = file.project_entry_id().unwrap();
125                            let mut parent_abs_path = file.worktree.read(cx).absolutize(&file.path);
126                            if !parent_abs_path.pop() {
127                                panic!("Invalid worktree path");
128                            }
129
130                            (project_entry_id, parent_abs_path, buffer.snapshot())
131                        })?;
132
133                    anyhow::Ok(
134                        cx.background_spawn(async move {
135                            let mut hasher = collections::FxHasher::default();
136                            snapshot.text().hash(&mut hasher);
137                            FileSnapshot {
138                                project_entry_id,
139                                snapshot,
140                                hash: hasher.finish(),
141                                parent_abs_path: parent_abs_path.into(),
142                            }
143                        })
144                        .await,
145                    )
146                })
147            })
148            .collect::<Vec<_>>()
149    })
150    .await?;
151
152    let mut file_snapshots = HashMap::default();
153    let mut hasher = collections::FxHasher::default();
154    for FileSnapshot {
155        project_entry_id,
156        snapshot,
157        hash,
158        ..
159    } in &files
160    {
161        file_snapshots.insert(*project_entry_id, snapshot.clone());
162        hash.hash(&mut hasher);
163    }
164    let files_hash = hasher.finish();
165    let file_snapshots = Arc::new(file_snapshots);
166
167    let lsp_definitions_path = std::env::current_dir()?.join(format!(
168        "target/zeta2-lsp-definitions-{:x}.jsonl",
169        files_hash
170    ));
171
172    let mut lsp_definitions = HashMap::default();
173    let mut lsp_files = 0;
174
175    if std::fs::exists(&lsp_definitions_path)? {
176        log::info!(
177            "Using cached LSP definitions from {}",
178            lsp_definitions_path.display()
179        );
180
181        let file = File::options()
182            .read(true)
183            .write(true)
184            .open(&lsp_definitions_path)?;
185        let lines = BufReader::new(&file).lines();
186        let mut valid_len: usize = 0;
187
188        for (line, expected_file) in lines.zip(files.iter()) {
189            let line = line?;
190            let FileLspDefinitions { path, references } = match serde_json::from_str(&line) {
191                Ok(ok) => ok,
192                Err(_) => {
193                    log::error!("Found invalid cache line. Truncating to #{lsp_files}.",);
194                    file.set_len(valid_len as u64)?;
195                    break;
196                }
197            };
198            let expected_path = expected_file.snapshot.file().unwrap().path().as_unix_str();
199            if expected_path != path.as_ref() {
200                log::error!(
201                    "Expected file #{} to be {expected_path}, but found {path}. Truncating to #{lsp_files}.",
202                    lsp_files + 1
203                );
204                file.set_len(valid_len as u64)?;
205                break;
206            }
207            for (point, ranges) in references {
208                let Ok(path) = RelPath::new(Path::new(path.as_ref()), PathStyle::Posix) else {
209                    log::warn!("Invalid path: {}", path);
210                    continue;
211                };
212                lsp_definitions.insert(
213                    SourceLocation {
214                        path: path.into_arc(),
215                        point: point.into(),
216                    },
217                    ranges,
218                );
219            }
220            lsp_files += 1;
221            valid_len += line.len() + 1
222        }
223    }
224
225    if lsp_files < files.len() {
226        if lsp_files == 0 {
227            log::warn!(
228                "No LSP definitions found, populating {}",
229                lsp_definitions_path.display()
230            );
231        } else {
232            log::warn!("{} files missing from LSP cache", files.len() - lsp_files);
233        }
234
235        gather_lsp_definitions(
236            &lsp_definitions_path,
237            lsp_files,
238            &filtered_files,
239            &worktree,
240            &project,
241            &mut lsp_definitions,
242            cx,
243        )
244        .await?;
245    }
246    let files_len = files.len().min(file_limit.unwrap_or(usize::MAX));
247    let done_count = Arc::new(AtomicUsize::new(0));
248
249    let (output_tx, mut output_rx) = mpsc::unbounded::<RetrievalStatsResult>();
250    let mut output = std::fs::File::create("target/zeta-retrieval-stats.txt")?;
251
252    let tasks = files
253        .into_iter()
254        .skip(skip_files.unwrap_or(0))
255        .take(file_limit.unwrap_or(usize::MAX))
256        .map(|project_file| {
257            let index_state = index_state.clone();
258            let lsp_definitions = lsp_definitions.clone();
259            let options = options.clone();
260            let output_tx = output_tx.clone();
261            let done_count = done_count.clone();
262            let file_snapshots = file_snapshots.clone();
263            cx.background_spawn(async move {
264                let snapshot = project_file.snapshot;
265
266                let full_range = 0..snapshot.len();
267                let references = references_in_range(
268                    full_range,
269                    &snapshot.text(),
270                    ReferenceRegion::Nearby,
271                    &snapshot,
272                );
273
274                println!("references: {}", references.len(),);
275
276                let imports = if options.context.use_imports {
277                    Imports::gather(&snapshot, Some(&project_file.parent_abs_path))
278                } else {
279                    Imports::default()
280                };
281
282                let path = snapshot.file().unwrap().path();
283
284                for reference in references {
285                    let query_point = snapshot.offset_to_point(reference.range.start);
286                    let source_location = SourceLocation {
287                        path: path.clone(),
288                        point: query_point,
289                    };
290                    let lsp_definitions = lsp_definitions
291                        .get(&source_location)
292                        .cloned()
293                        .unwrap_or_else(|| {
294                            log::warn!(
295                                "No definitions found for source location: {:?}",
296                                source_location
297                            );
298                            Vec::new()
299                        });
300
301                    let retrieve_result = retrieve_definitions(
302                        &reference,
303                        &imports,
304                        query_point,
305                        &snapshot,
306                        &index_state,
307                        &file_snapshots,
308                        &options,
309                    )
310                    .await?;
311
312                    // TODO: LSP returns things like locals, this filters out some of those, but potentially
313                    // hides some retrieval issues.
314                    if retrieve_result.definitions.is_empty() {
315                        continue;
316                    }
317
318                    let mut best_match = None;
319                    let mut has_external_definition = false;
320                    let mut in_excerpt = false;
321                    for (index, retrieved_definition) in
322                        retrieve_result.definitions.iter().enumerate()
323                    {
324                        for lsp_definition in &lsp_definitions {
325                            let SourceRange {
326                                path,
327                                point_range,
328                                offset_range,
329                            } = lsp_definition;
330                            let lsp_point_range =
331                                SerializablePoint::into_language_point_range(point_range.clone());
332                            has_external_definition = has_external_definition
333                                || path.is_absolute()
334                                || path
335                                    .components()
336                                    .any(|component| component.as_os_str() == "node_modules");
337                            let is_match = path.as_path()
338                                == retrieved_definition.path.as_std_path()
339                                && retrieved_definition
340                                    .range
341                                    .contains_inclusive(&lsp_point_range);
342                            if is_match {
343                                if best_match.is_none() {
344                                    best_match = Some(index);
345                                }
346                            }
347                            in_excerpt = in_excerpt
348                                || retrieve_result.excerpt_range.as_ref().is_some_and(
349                                    |excerpt_range| excerpt_range.contains_inclusive(&offset_range),
350                                );
351                        }
352                    }
353
354                    let outcome = if let Some(best_match) = best_match {
355                        RetrievalOutcome::Match { best_match }
356                    } else if has_external_definition {
357                        RetrievalOutcome::NoMatchDueToExternalLspDefinitions
358                    } else if in_excerpt {
359                        RetrievalOutcome::ProbablyLocal
360                    } else {
361                        RetrievalOutcome::NoMatch
362                    };
363
364                    let result = RetrievalStatsResult {
365                        outcome,
366                        path: path.clone(),
367                        identifier: reference.identifier,
368                        point: query_point,
369                        lsp_definitions,
370                        retrieved_definitions: retrieve_result.definitions,
371                    };
372
373                    output_tx.unbounded_send(result).ok();
374                }
375
376                println!(
377                    "{:02}/{:02} done",
378                    done_count.fetch_add(1, atomic::Ordering::Relaxed) + 1,
379                    files_len,
380                );
381
382                anyhow::Ok(())
383            })
384        })
385        .collect::<Vec<_>>();
386
387    drop(output_tx);
388
389    let results_task = cx.background_spawn(async move {
390        let mut results = Vec::new();
391        while let Some(result) = output_rx.next().await {
392            output
393                .write_all(format!("{:#?}\n", result).as_bytes())
394                .log_err();
395            results.push(result)
396        }
397        results
398    });
399
400    futures::future::try_join_all(tasks).await?;
401    println!("Tasks completed");
402    let results = results_task.await;
403    println!("Results received");
404
405    let mut references_count = 0;
406
407    let mut included_count = 0;
408    let mut both_absent_count = 0;
409
410    let mut retrieved_count = 0;
411    let mut top_match_count = 0;
412    let mut non_top_match_count = 0;
413    let mut ranking_involved_top_match_count = 0;
414
415    let mut no_match_count = 0;
416    let mut no_match_none_retrieved = 0;
417    let mut no_match_wrong_retrieval = 0;
418
419    let mut expected_no_match_count = 0;
420    let mut in_excerpt_count = 0;
421    let mut external_definition_count = 0;
422
423    for result in results {
424        references_count += 1;
425        match &result.outcome {
426            RetrievalOutcome::Match { best_match } => {
427                included_count += 1;
428                retrieved_count += 1;
429                let multiple = result.retrieved_definitions.len() > 1;
430                if *best_match == 0 {
431                    top_match_count += 1;
432                    if multiple {
433                        ranking_involved_top_match_count += 1;
434                    }
435                } else {
436                    non_top_match_count += 1;
437                }
438            }
439            RetrievalOutcome::NoMatch => {
440                if result.lsp_definitions.is_empty() {
441                    included_count += 1;
442                    both_absent_count += 1;
443                } else {
444                    no_match_count += 1;
445                    if result.retrieved_definitions.is_empty() {
446                        no_match_none_retrieved += 1;
447                    } else {
448                        no_match_wrong_retrieval += 1;
449                    }
450                }
451            }
452            RetrievalOutcome::NoMatchDueToExternalLspDefinitions => {
453                expected_no_match_count += 1;
454                external_definition_count += 1;
455            }
456            RetrievalOutcome::ProbablyLocal => {
457                included_count += 1;
458                in_excerpt_count += 1;
459            }
460        }
461    }
462
463    fn count_and_percentage(part: usize, total: usize) -> String {
464        format!("{} ({:.2}%)", part, (part as f64 / total as f64) * 100.0)
465    }
466
467    println!("");
468    println!("╮ references: {}", references_count);
469    println!(
470        "├─╮ included: {}",
471        count_and_percentage(included_count, references_count),
472    );
473    println!(
474        "│ ├─╮ retrieved: {}",
475        count_and_percentage(retrieved_count, references_count)
476    );
477    println!(
478        "│ │ ├─╮ top match : {}",
479        count_and_percentage(top_match_count, retrieved_count)
480    );
481    println!(
482        "│ │ │ ╰─╴ involving ranking: {}",
483        count_and_percentage(ranking_involved_top_match_count, top_match_count)
484    );
485    println!(
486        "│ │ ╰─╴ non-top match: {}",
487        count_and_percentage(non_top_match_count, retrieved_count)
488    );
489    println!(
490        "│ ├─╴ both absent: {}",
491        count_and_percentage(both_absent_count, included_count)
492    );
493    println!(
494        "│ ╰─╴ in excerpt: {}",
495        count_and_percentage(in_excerpt_count, included_count)
496    );
497    println!(
498        "├─╮ no match: {}",
499        count_and_percentage(no_match_count, references_count)
500    );
501    println!(
502        "│ ├─╴ none retrieved: {}",
503        count_and_percentage(no_match_none_retrieved, no_match_count)
504    );
505    println!(
506        "│ ╰─╴ wrong retrieval: {}",
507        count_and_percentage(no_match_wrong_retrieval, no_match_count)
508    );
509    println!(
510        "╰─╮ expected no match: {}",
511        count_and_percentage(expected_no_match_count, references_count)
512    );
513    println!(
514        "  ╰─╴ external definition: {}",
515        count_and_percentage(external_definition_count, expected_no_match_count)
516    );
517
518    println!("");
519    println!("LSP definition cache at {}", lsp_definitions_path.display());
520
521    Ok("".to_string())
522}
523
524struct RetrieveResult {
525    definitions: Vec<RetrievedDefinition>,
526    excerpt_range: Option<Range<usize>>,
527}
528
529async fn retrieve_definitions(
530    reference: &Reference,
531    imports: &Imports,
532    query_point: Point,
533    snapshot: &BufferSnapshot,
534    index: &Arc<SyntaxIndexState>,
535    file_snapshots: &Arc<HashMap<ProjectEntryId, BufferSnapshot>>,
536    options: &Arc<zeta2::ZetaOptions>,
537) -> Result<RetrieveResult> {
538    let mut single_reference_map = HashMap::default();
539    single_reference_map.insert(reference.identifier.clone(), vec![reference.clone()]);
540    let edit_prediction_context = EditPredictionContext::gather_context_with_references_fn(
541        query_point,
542        snapshot,
543        imports,
544        &options.context,
545        Some(&index),
546        |_, _, _| single_reference_map,
547    );
548
549    let Some(edit_prediction_context) = edit_prediction_context else {
550        return Ok(RetrieveResult {
551            definitions: Vec::new(),
552            excerpt_range: None,
553        });
554    };
555
556    let mut retrieved_definitions = Vec::new();
557    for scored_declaration in edit_prediction_context.declarations {
558        match &scored_declaration.declaration {
559            Declaration::File {
560                project_entry_id,
561                declaration,
562                ..
563            } => {
564                let Some(snapshot) = file_snapshots.get(&project_entry_id) else {
565                    log::error!("bug: file project entry not found");
566                    continue;
567                };
568                let path = snapshot.file().unwrap().path().clone();
569                retrieved_definitions.push(RetrievedDefinition {
570                    path,
571                    range: snapshot.offset_to_point(declaration.item_range.start)
572                        ..snapshot.offset_to_point(declaration.item_range.end),
573                    score: scored_declaration.score(DeclarationStyle::Declaration),
574                    retrieval_score: scored_declaration.retrieval_score(),
575                    components: scored_declaration.components,
576                });
577            }
578            Declaration::Buffer {
579                project_entry_id,
580                rope,
581                declaration,
582                ..
583            } => {
584                let Some(snapshot) = file_snapshots.get(&project_entry_id) else {
585                    // This case happens when dependency buffers have been opened by
586                    // go-to-definition, resulting in single-file worktrees.
587                    continue;
588                };
589                let path = snapshot.file().unwrap().path().clone();
590                retrieved_definitions.push(RetrievedDefinition {
591                    path,
592                    range: rope.offset_to_point(declaration.item_range.start)
593                        ..rope.offset_to_point(declaration.item_range.end),
594                    score: scored_declaration.score(DeclarationStyle::Declaration),
595                    retrieval_score: scored_declaration.retrieval_score(),
596                    components: scored_declaration.components,
597                });
598            }
599        }
600    }
601    retrieved_definitions.sort_by_key(|definition| Reverse(OrderedFloat(definition.score)));
602
603    Ok(RetrieveResult {
604        definitions: retrieved_definitions,
605        excerpt_range: Some(edit_prediction_context.excerpt.range),
606    })
607}
608
609async fn gather_lsp_definitions(
610    lsp_definitions_path: &Path,
611    start_index: usize,
612    files: &[ProjectPath],
613    worktree: &Entity<Worktree>,
614    project: &Entity<Project>,
615    definitions: &mut HashMap<SourceLocation, Vec<SourceRange>>,
616    cx: &mut AsyncApp,
617) -> Result<()> {
618    let worktree_id = worktree.read_with(cx, |worktree, _cx| worktree.id())?;
619
620    let lsp_store = project.read_with(cx, |project, _cx| project.lsp_store())?;
621    cx.subscribe(&lsp_store, {
622        move |_, event, _| {
623            if let project::LspStoreEvent::LanguageServerUpdate {
624                message:
625                    client::proto::update_language_server::Variant::WorkProgress(
626                        client::proto::LspWorkProgress {
627                            message: Some(message),
628                            ..
629                        },
630                    ),
631                ..
632            } = event
633            {
634                println!("⟲ {message}")
635            }
636        }
637    })?
638    .detach();
639
640    let (cache_line_tx, mut cache_line_rx) = mpsc::unbounded::<FileLspDefinitions>();
641
642    let cache_file = File::options()
643        .append(true)
644        .create(true)
645        .open(lsp_definitions_path)
646        .unwrap();
647
648    let cache_task = cx.background_spawn(async move {
649        let mut writer = BufWriter::new(cache_file);
650        while let Some(line) = cache_line_rx.next().await {
651            serde_json::to_writer(&mut writer, &line).unwrap();
652            writer.write_all(&[b'\n']).unwrap();
653        }
654        writer.flush().unwrap();
655    });
656
657    let mut error_count = 0;
658    let mut lsp_open_handles = Vec::new();
659    let mut ready_languages = HashSet::default();
660    for (file_index, project_path) in files[start_index..].iter().enumerate() {
661        println!(
662            "Processing file {} of {}: {}",
663            start_index + file_index + 1,
664            files.len(),
665            project_path.path.display(PathStyle::Posix)
666        );
667
668        let Some((lsp_open_handle, language_server_id, buffer)) = open_buffer_with_language_server(
669            project.clone(),
670            worktree.clone(),
671            project_path.path.clone(),
672            &mut ready_languages,
673            cx,
674        )
675        .await
676        .log_err() else {
677            continue;
678        };
679        lsp_open_handles.push(lsp_open_handle);
680
681        let snapshot = buffer.read_with(cx, |buffer, _cx| buffer.snapshot())?;
682        let full_range = 0..snapshot.len();
683        let references = references_in_range(
684            full_range,
685            &snapshot.text(),
686            ReferenceRegion::Nearby,
687            &snapshot,
688        );
689
690        loop {
691            let is_ready = lsp_store
692                .read_with(cx, |lsp_store, _cx| {
693                    lsp_store
694                        .language_server_statuses
695                        .get(&language_server_id)
696                        .is_some_and(|status| status.pending_work.is_empty())
697                })
698                .unwrap();
699            if is_ready {
700                break;
701            }
702            cx.background_executor()
703                .timer(Duration::from_millis(10))
704                .await;
705        }
706
707        let mut cache_line_references = Vec::with_capacity(references.len());
708
709        for reference in references {
710            // TODO: Rename declaration to definition in edit_prediction_context?
711            let lsp_result = project
712                .update(cx, |project, cx| {
713                    project.definitions(&buffer, reference.range.start, cx)
714                })?
715                .await;
716
717            match lsp_result {
718                Ok(lsp_definitions) => {
719                    let mut targets = Vec::new();
720                    for target in lsp_definitions.unwrap_or_default() {
721                        let buffer = target.target.buffer;
722                        let anchor_range = target.target.range;
723                        buffer.read_with(cx, |buffer, cx| {
724                            let Some(file) = project::File::from_dyn(buffer.file()) else {
725                                return;
726                            };
727                            let file_worktree = file.worktree.read(cx);
728                            let file_worktree_id = file_worktree.id();
729                            // Relative paths for worktree files, absolute for all others
730                            let path = if worktree_id != file_worktree_id {
731                                file.worktree.read(cx).absolutize(&file.path)
732                            } else {
733                                file.path.as_std_path().to_path_buf()
734                            };
735                            let offset_range = anchor_range.to_offset(&buffer);
736                            let point_range = SerializablePoint::from_language_point_range(
737                                offset_range.to_point(&buffer),
738                            );
739                            targets.push(SourceRange {
740                                path,
741                                offset_range,
742                                point_range,
743                            });
744                        })?;
745                    }
746
747                    let point = snapshot.offset_to_point(reference.range.start);
748
749                    cache_line_references.push((point.into(), targets.clone()));
750                    definitions.insert(
751                        SourceLocation {
752                            path: project_path.path.clone(),
753                            point,
754                        },
755                        targets,
756                    );
757                }
758                Err(err) => {
759                    log::error!("Language server error: {err}");
760                    error_count += 1;
761                }
762            }
763        }
764
765        cache_line_tx
766            .unbounded_send(FileLspDefinitions {
767                path: project_path.path.as_unix_str().into(),
768                references: cache_line_references,
769            })
770            .log_err();
771    }
772
773    drop(cache_line_tx);
774
775    if error_count > 0 {
776        log::error!("Encountered {} language server errors", error_count);
777    }
778
779    cache_task.await;
780
781    Ok(())
782}
783
784#[derive(Serialize, Deserialize)]
785struct FileLspDefinitions {
786    path: Arc<str>,
787    references: Vec<(SerializablePoint, Vec<SourceRange>)>,
788}
789
790#[derive(Debug, Clone, Serialize, Deserialize)]
791struct SourceRange {
792    path: PathBuf,
793    point_range: Range<SerializablePoint>,
794    offset_range: Range<usize>,
795}
796
797/// Serializes to 1-based row and column indices.
798#[derive(Debug, Clone, Serialize, Deserialize)]
799pub struct SerializablePoint {
800    pub row: u32,
801    pub column: u32,
802}
803
804impl SerializablePoint {
805    pub fn into_language_point_range(range: Range<Self>) -> Range<Point> {
806        range.start.into()..range.end.into()
807    }
808
809    pub fn from_language_point_range(range: Range<Point>) -> Range<Self> {
810        range.start.into()..range.end.into()
811    }
812}
813
814impl From<Point> for SerializablePoint {
815    fn from(point: Point) -> Self {
816        SerializablePoint {
817            row: point.row + 1,
818            column: point.column + 1,
819        }
820    }
821}
822
823impl From<SerializablePoint> for Point {
824    fn from(serializable: SerializablePoint) -> Self {
825        Point {
826            row: serializable.row.saturating_sub(1),
827            column: serializable.column.saturating_sub(1),
828        }
829    }
830}
831
832#[derive(Debug)]
833struct RetrievalStatsResult {
834    outcome: RetrievalOutcome,
835    #[allow(dead_code)]
836    path: Arc<RelPath>,
837    #[allow(dead_code)]
838    identifier: Identifier,
839    #[allow(dead_code)]
840    point: Point,
841    #[allow(dead_code)]
842    lsp_definitions: Vec<SourceRange>,
843    retrieved_definitions: Vec<RetrievedDefinition>,
844}
845
846#[derive(Debug)]
847enum RetrievalOutcome {
848    Match {
849        /// Lowest index within retrieved_definitions that matches an LSP definition.
850        best_match: usize,
851    },
852    ProbablyLocal,
853    NoMatch,
854    NoMatchDueToExternalLspDefinitions,
855}
856
857#[derive(Debug)]
858struct RetrievedDefinition {
859    path: Arc<RelPath>,
860    range: Range<Point>,
861    score: f32,
862    #[allow(dead_code)]
863    retrieval_score: f32,
864    #[allow(dead_code)]
865    components: DeclarationScoreComponents,
866}