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