Skip to main content

searchfox_lib/
definition.rs

1use crate::client::SearchfoxClient;
2use crate::search::SearchOptions;
3use crate::utils::extract_complete_method;
4use anyhow::Result;
5use log::{debug, error};
6
7impl SearchfoxClient {
8    pub async fn get_definition_context(
9        &self,
10        file_path: &str,
11        line_number: usize,
12        context_lines: usize,
13    ) -> Result<String> {
14        let file_content = self.get_file(file_path).await?;
15        let lines: Vec<&str> = file_content.lines().collect();
16
17        let (_, method_lines) = extract_complete_method(&lines, line_number);
18
19        if method_lines.len() > 1 {
20            return Ok(method_lines.join("\n"));
21        }
22
23        let start_line = if line_number > context_lines {
24            line_number - context_lines
25        } else {
26            1
27        };
28        let end_line = std::cmp::min(line_number + context_lines, lines.len());
29
30        let mut result = String::new();
31        for (i, line) in lines.iter().enumerate() {
32            let line_num = i + 1;
33            if line_num >= start_line && line_num <= end_line {
34                let marker = if line_num == line_number {
35                    ">>>"
36                } else {
37                    "   "
38                };
39                result.push_str(&format!("{marker} {line_num:4}: {line}\n"));
40            }
41        }
42
43        Ok(result)
44    }
45
46    pub async fn find_and_display_definition(
47        &self,
48        symbol: &str,
49        path_filter: Option<&str>,
50        options: &SearchOptions,
51    ) -> Result<String> {
52        debug!("Finding potential definition locations...");
53        let file_locations = self
54            .find_symbol_locations(symbol, path_filter, options)
55            .await?;
56
57        if file_locations.is_empty() {
58            error!("No potential definitions found for '{symbol}'");
59            return Ok(String::new());
60        }
61
62        debug!(
63            "Found {} potential definition location(s)",
64            file_locations.len()
65        );
66
67        let is_ctor = symbol.rfind("::").is_some_and(|pos| {
68            let class_part = &symbol[..pos];
69            let method_part = &symbol[pos + 2..];
70            let class_name = class_part.split("::").last().unwrap_or(class_part);
71            class_name == method_part
72        });
73
74        let mut results = Vec::new();
75        let mut last_error = None;
76        for (file_path, line_number) in &file_locations {
77            let context_lines = if is_ctor { 2 } else { 10 };
78            match self
79                .get_definition_context(file_path, *line_number, context_lines)
80                .await
81            {
82                Ok(context) => {
83                    if !context.is_empty() {
84                        results.push(context);
85                    }
86                }
87                Err(e) => {
88                    error!("Could not fetch context: {e}");
89                    last_error = Some(e);
90                }
91            }
92        }
93
94        if results.is_empty() {
95            // Locations were found, so an empty result here means every context
96            // fetch failed. Surface that rather than reporting a missing symbol.
97            if let Some(e) = last_error {
98                return Err(e.context(format!(
99                    "found {} location(s) for '{symbol}' but could not fetch any file contents",
100                    file_locations.len()
101                )));
102            }
103            error!("No definition found for symbol '{symbol}'");
104            Ok(String::new())
105        } else if results.len() == 1 {
106            Ok(results[0].clone())
107        } else {
108            Ok(results.join("\n\n"))
109        }
110    }
111}