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}