searchfox_lib/
utils.rs

1use crate::types::Line;
2
3pub fn is_mozilla_repository() -> bool {
4    std::path::Path::new("./mach").exists()
5}
6
7pub fn read_local_file(file_path: &str) -> Option<String> {
8    if let Ok(content) = std::fs::read_to_string(file_path) {
9        return Some(content);
10    }
11    if let Ok(content) = std::fs::read_to_string(format!("./{file_path}")) {
12        return Some(content);
13    }
14    None
15}
16
17pub fn find_symbol_in_local_content(
18    content: &str,
19    expected_line: usize,
20    symbol: &str,
21) -> Option<usize> {
22    let lines: Vec<&str> = content.lines().collect();
23
24    if expected_line > 0 && expected_line <= lines.len() {
25        let line_idx = expected_line - 1;
26        if lines[line_idx].contains(symbol)
27            || (symbol.contains("::")
28                && lines[line_idx].contains(symbol.split("::").last().unwrap_or("")))
29        {
30            return Some(expected_line);
31        }
32    }
33
34    let search_range = 50;
35    let start = expected_line.saturating_sub(search_range);
36    let end = std::cmp::min(expected_line + search_range, lines.len());
37
38    for i in start..end {
39        if i < lines.len() {
40            let line = lines[i];
41            if (line.contains(symbol)
42                || (symbol.contains("::")
43                    && line.contains(symbol.split("::").last().unwrap_or(""))))
44                && (line.contains("::") || line.contains("(") || line.contains("="))
45            {
46                return Some(i + 1);
47            }
48        }
49    }
50
51    None
52}
53
54pub fn extract_complete_method(lines: &[&str], start_line: usize) -> (usize, Vec<String>) {
55    let start_idx = start_line.saturating_sub(1);
56    if start_idx >= lines.len() {
57        return (
58            start_line,
59            vec![lines.get(start_idx).unwrap_or(&"").to_string()],
60        );
61    }
62
63    let start_line_content = lines[start_idx];
64
65    let looks_like_function = (start_line_content.contains("(")
66        && (start_line_content.contains("{")
67            || start_line_content.trim_end().ends_with(")")
68            || start_line_content.trim_end().ends_with(";")
69            || start_line_content.contains("::")
70            || start_line_content.trim_start().starts_with("fn ")
71            || start_line_content.contains("function ")))
72        || start_line_content.contains("class ")
73        || start_line_content.contains("struct ")
74        || start_line_content.contains("interface ");
75
76    if !looks_like_function {
77        let mut found_function_pattern = false;
78        for i in 0..=5.min(lines.len().saturating_sub(start_idx + 1)) {
79            if let Some(line) = lines.get(start_idx + i) {
80                if line.contains("{") || line.trim_start().starts_with(":") {
81                    found_function_pattern = true;
82                    break;
83                }
84            }
85        }
86
87        if !found_function_pattern {
88            let context_start = start_idx.saturating_sub(5);
89            let context_end = std::cmp::min(start_idx + 5, lines.len());
90            let context_lines: Vec<String> = lines[context_start..context_end]
91                .iter()
92                .enumerate()
93                .map(|(i, line)| {
94                    let line_num = context_start + i + 1;
95                    let marker = if line_num == start_line { ">>>" } else { "   " };
96                    format!("{marker} {line_num:4}: {line}")
97                })
98                .collect();
99            return (start_line, context_lines);
100        }
101    }
102
103    if start_line_content.trim_end().ends_with(';')
104        && !(start_line_content.contains("class ") || start_line_content.contains("struct "))
105    {
106        return (
107            start_line,
108            vec![format!(">>> {:4}: {}", start_line, start_line_content)],
109        );
110    }
111
112    let mut found_opening_brace = start_line_content.contains('{');
113
114    if !found_opening_brace {
115        for (i, line) in lines.iter().enumerate().skip(start_idx + 1) {
116            if line.contains('{') {
117                found_opening_brace = true;
118                break;
119            }
120            if i > start_idx + 25
121                || (line.trim().is_empty() && i > start_idx + 5)
122                || (line.contains("::")
123                    && line.contains("(")
124                    && !line.trim_start().starts_with("//")
125                    && !line.contains("mId")
126                    && !line.contains("m"))
127            {
128                break;
129            }
130        }
131    }
132
133    if !found_opening_brace {
134        return (
135            start_line,
136            vec![format!(">>> {:4}: {}", start_line, start_line_content)],
137        );
138    }
139
140    let mut result_lines = Vec::new();
141    let mut brace_count = 0;
142    let mut in_string = false;
143    let mut in_char = false;
144    let mut escaped = false;
145    let mut in_single_comment = false;
146    let mut in_multi_comment = false;
147
148    for (i, line) in lines.iter().enumerate().skip(start_idx) {
149        let line_num = i + 1;
150        let marker = if line_num == start_line { ">>>" } else { "   " };
151        result_lines.push(format!("{marker} {line_num:4}: {line}"));
152
153        let chars: Vec<char> = line.chars().collect();
154        let mut j = 0;
155        while j < chars.len() {
156            let ch = chars[j];
157            let next_ch = chars.get(j + 1).copied();
158
159            if escaped {
160                escaped = false;
161                j += 1;
162                continue;
163            }
164
165            match ch {
166                '\\' if in_string || in_char => escaped = true,
167                '"' if !in_char && !in_single_comment && !in_multi_comment => {
168                    in_string = !in_string
169                }
170                '\'' if !in_string && !in_single_comment && !in_multi_comment => in_char = !in_char,
171                '/' if !in_string && !in_char && !in_single_comment && !in_multi_comment => {
172                    if next_ch == Some('/') {
173                        in_single_comment = true;
174                        j += 1;
175                    } else if next_ch == Some('*') {
176                        in_multi_comment = true;
177                        j += 1;
178                    }
179                }
180                '*' if in_multi_comment && next_ch == Some('/') => {
181                    in_multi_comment = false;
182                    j += 1;
183                }
184                '{' if !in_string && !in_char && !in_single_comment && !in_multi_comment => {
185                    brace_count += 1;
186                }
187                '}' if !in_string && !in_char && !in_single_comment && !in_multi_comment => {
188                    brace_count -= 1;
189                    if brace_count == 0 {
190                        let is_class_or_struct = lines[start_idx].contains("class ")
191                            || lines[start_idx].contains("struct ");
192                        if is_class_or_struct {
193                            let remaining_on_line = &line[j + 1..];
194                            if remaining_on_line.trim().starts_with(';') {
195                                return (start_line, result_lines);
196                            } else if i + 1 < lines.len() {
197                                let next_line = lines[i + 1];
198                                if next_line.trim().starts_with(';') {
199                                    result_lines.push(format!("     {:4}: {}", i + 2, next_line));
200                                }
201                            }
202                        }
203                        return (start_line, result_lines);
204                    }
205                }
206                _ => {}
207            }
208            j += 1;
209        }
210
211        in_single_comment = false;
212
213        if result_lines.len() > 200 {
214            result_lines.push("   ...  : (method too long, truncated)".to_string());
215            break;
216        }
217    }
218
219    (start_line, result_lines)
220}
221
222pub fn is_potential_definition(line: &Line, query: &str) -> bool {
223    let line_text = &line.line;
224    let line_lower = line_text.to_lowercase();
225    let query_lower = query.to_lowercase();
226
227    let contains_query = line_text.contains(query) || line_lower.contains(&query_lower);
228
229    if contains_query {
230        let looks_like_definition = line_text.contains("{")
231            || line_text.trim_end().ends_with(';')
232            || line_text.contains("=")
233            || line_text.contains("class ")
234            || line_text.contains("struct ")
235            || line_text.contains("interface ")
236            || (line_text.contains("::")
237                && (line_text.contains("(")
238                    || line_text.contains("already_AddRefed")
239                    || line_text.contains("RefPtr")
240                    || line_text.contains("nsCOMPtr")));
241
242        looks_like_definition
243    } else {
244        false
245    }
246}
247
248pub fn get_github_raw_url(repo: &str, file_path: &str) -> String {
249    let github_repo = match repo {
250        "comm-central" => "mozilla/releases-comm-central",
251        _ => "mozilla/firefox",
252    };
253
254    let branch = match repo {
255        "mozilla-central" => "main",
256        "autoland" => "autoland",
257        "mozilla-beta" => "beta",
258        "mozilla-release" => "release",
259        "mozilla-esr115" => "esr115",
260        "mozilla-esr128" => "esr128",
261        "mozilla-esr140" => "esr140",
262        "comm-central" => "main",
263        _ => "main",
264    };
265
266    format!("https://raw.githubusercontent.com/{github_repo}/{branch}/{file_path}")
267}