Skip to main content

searchfox_lib/
definition.rs

1use crate::client::SearchfoxClient;
2use crate::search::SearchOptions;
3use crate::utils::{
4    extract_complete_method, find_symbol_in_local_content, is_mozilla_repository, read_local_file,
5};
6use anyhow::Result;
7use log::{debug, error};
8
9impl SearchfoxClient {
10    pub async fn get_definition_context(
11        &self,
12        file_path: &str,
13        line_number: usize,
14        context_lines: usize,
15        symbol_name: Option<&str>,
16    ) -> Result<String> {
17        if is_mozilla_repository() {
18            if let Some(local_content) = read_local_file(file_path) {
19                let lines: Vec<&str> = local_content.lines().collect();
20
21                let actual_line = if line_number > 0 && line_number <= lines.len() {
22                    let line_idx = line_number - 1;
23                    let line_content = lines[line_idx];
24
25                    let looks_correct = if let Some(symbol) = symbol_name {
26                        line_content.contains(symbol)
27                            || (symbol.contains("::")
28                                && line_content.contains(symbol.split("::").last().unwrap_or("")))
29                    } else {
30                        line_content.contains("::") || line_content.contains("(")
31                    };
32
33                    if looks_correct {
34                        Some(line_number)
35                    } else if let Some(symbol) = symbol_name {
36                        find_symbol_in_local_content(&local_content, line_number, symbol)
37                    } else {
38                        None
39                    }
40                } else if let Some(symbol) = symbol_name {
41                    find_symbol_in_local_content(&local_content, 1, symbol)
42                } else {
43                    None
44                };
45
46                let final_line = actual_line.unwrap_or(line_number);
47
48                let (_, method_lines) = extract_complete_method(&lines, final_line);
49
50                if method_lines.len() > 1 {
51                    return Ok(method_lines.join("\n"));
52                }
53
54                let start_line = if final_line > context_lines {
55                    final_line - context_lines
56                } else {
57                    1
58                };
59                let end_line = std::cmp::min(final_line + context_lines, lines.len());
60
61                let mut result = String::new();
62                for (i, line) in lines.iter().enumerate() {
63                    let line_num = i + 1;
64                    if line_num >= start_line && line_num <= end_line {
65                        let marker = if line_num == final_line { ">>>" } else { "   " };
66                        result.push_str(&format!("{marker} {line_num:4}: {line}\n"));
67                    }
68                }
69
70                return Ok(result);
71            }
72        }
73
74        let file_content = self.get_file(file_path).await?;
75        let lines: Vec<&str> = file_content.lines().collect();
76
77        let (_, method_lines) = extract_complete_method(&lines, line_number);
78
79        if method_lines.len() > 1 {
80            return Ok(method_lines.join("\n"));
81        }
82
83        let start_line = if line_number > context_lines {
84            line_number - context_lines
85        } else {
86            1
87        };
88        let end_line = std::cmp::min(line_number + context_lines, lines.len());
89
90        let mut result = String::new();
91        for (i, line) in lines.iter().enumerate() {
92            let line_num = i + 1;
93            if line_num >= start_line && line_num <= end_line {
94                let marker = if line_num == line_number {
95                    ">>>"
96                } else {
97                    "   "
98                };
99                result.push_str(&format!("{marker} {line_num:4}: {line}\n"));
100            }
101        }
102
103        Ok(result)
104    }
105
106    pub async fn find_and_display_definition(
107        &self,
108        symbol: &str,
109        path_filter: Option<&str>,
110        options: &SearchOptions,
111    ) -> Result<String> {
112        debug!("Finding potential definition locations...");
113        let file_locations = self
114            .find_symbol_locations(symbol, path_filter, options)
115            .await?;
116
117        if file_locations.is_empty() {
118            error!("No potential definitions found for '{symbol}'");
119            return Ok(String::new());
120        }
121
122        debug!(
123            "Found {} potential definition location(s)",
124            file_locations.len()
125        );
126
127        let is_ctor = symbol.rfind("::").is_some_and(|pos| {
128            let class_part = &symbol[..pos];
129            let method_part = &symbol[pos + 2..];
130            let class_name = class_part.split("::").last().unwrap_or(class_part);
131            class_name == method_part
132        });
133
134        let mut results = Vec::new();
135        for (file_path, line_number) in &file_locations {
136            let context_lines = if is_ctor { 2 } else { 10 };
137            match self
138                .get_definition_context(file_path, *line_number, context_lines, Some(symbol))
139                .await
140            {
141                Ok(context) => {
142                    if !context.is_empty() {
143                        results.push(context);
144                    }
145                }
146                Err(e) => {
147                    error!("Could not fetch context: {e}");
148                }
149            }
150        }
151
152        if results.is_empty() {
153            error!("No definition found for symbol '{symbol}'");
154            Ok(String::new())
155        } else if results.len() == 1 {
156            Ok(results[0].clone())
157        } else {
158            Ok(results.join("\n\n"))
159        }
160    }
161}