Skip to main content

searchfox_lib/
utils.rs

1use crate::types::Line;
2
3pub fn extract_complete_method(lines: &[&str], start_line: usize) -> (usize, Vec<String>) {
4    let start_idx = start_line.saturating_sub(1);
5    if start_idx >= lines.len() {
6        return (
7            start_line,
8            vec![lines.get(start_idx).unwrap_or(&"").to_string()],
9        );
10    }
11
12    let start_line_content = lines[start_idx];
13
14    let looks_like_function = (start_line_content.contains("(")
15        && (start_line_content.contains("{")
16            || start_line_content.trim_end().ends_with(")")
17            || start_line_content.trim_end().ends_with(";")
18            || start_line_content.contains("::")
19            || start_line_content.trim_start().starts_with("fn ")
20            || start_line_content.contains("function ")))
21        || start_line_content.contains("class ")
22        || start_line_content.contains("struct ")
23        || start_line_content.contains("interface ");
24
25    if !looks_like_function {
26        let mut found_function_pattern = false;
27        for i in 0..=5.min(lines.len().saturating_sub(start_idx + 1)) {
28            if let Some(line) = lines.get(start_idx + i) {
29                if line.contains("{") || line.trim_start().starts_with(":") {
30                    found_function_pattern = true;
31                    break;
32                }
33            }
34        }
35
36        if !found_function_pattern {
37            let context_start = start_idx.saturating_sub(5);
38            let context_end = std::cmp::min(start_idx + 5, lines.len());
39            let context_lines: Vec<String> = lines[context_start..context_end]
40                .iter()
41                .enumerate()
42                .map(|(i, line)| {
43                    let line_num = context_start + i + 1;
44                    let marker = if line_num == start_line { ">>>" } else { "   " };
45                    format!("{marker} {line_num:4}: {line}")
46                })
47                .collect();
48            return (start_line, context_lines);
49        }
50    }
51
52    if start_line_content.trim_end().ends_with(';')
53        && !(start_line_content.contains("class ") || start_line_content.contains("struct "))
54    {
55        return (
56            start_line,
57            vec![format!(">>> {:4}: {}", start_line, start_line_content)],
58        );
59    }
60
61    let mut found_opening_brace = start_line_content.contains('{');
62
63    if !found_opening_brace {
64        for (i, line) in lines.iter().enumerate().skip(start_idx + 1) {
65            if line.contains('{') {
66                found_opening_brace = true;
67                break;
68            }
69            if i > start_idx + 25
70                || (line.trim().is_empty() && i > start_idx + 5)
71                || (line.contains("::")
72                    && line.contains("(")
73                    && !line.trim_start().starts_with("//")
74                    && !line.contains("mId")
75                    && !line.contains("m"))
76            {
77                break;
78            }
79        }
80    }
81
82    if !found_opening_brace {
83        return (
84            start_line,
85            vec![format!(">>> {:4}: {}", start_line, start_line_content)],
86        );
87    }
88
89    let mut result_lines = Vec::new();
90    let mut brace_count = 0;
91    let mut in_string = false;
92    let mut in_char = false;
93    let mut escaped = false;
94    let mut in_single_comment = false;
95    let mut in_multi_comment = false;
96
97    for (i, line) in lines.iter().enumerate().skip(start_idx) {
98        let line_num = i + 1;
99        let marker = if line_num == start_line { ">>>" } else { "   " };
100        result_lines.push(format!("{marker} {line_num:4}: {line}"));
101
102        let chars: Vec<char> = line.chars().collect();
103        let mut j = 0;
104        while j < chars.len() {
105            let ch = chars[j];
106            let next_ch = chars.get(j + 1).copied();
107
108            if escaped {
109                escaped = false;
110                j += 1;
111                continue;
112            }
113
114            match ch {
115                '\\' if in_string || in_char => escaped = true,
116                '"' if !in_char && !in_single_comment && !in_multi_comment => {
117                    in_string = !in_string
118                }
119                '\'' if !in_string && !in_single_comment && !in_multi_comment => in_char = !in_char,
120                '/' if !in_string && !in_char && !in_single_comment && !in_multi_comment => {
121                    if next_ch == Some('/') {
122                        in_single_comment = true;
123                        j += 1;
124                    } else if next_ch == Some('*') {
125                        in_multi_comment = true;
126                        j += 1;
127                    }
128                }
129                '*' if in_multi_comment && next_ch == Some('/') => {
130                    in_multi_comment = false;
131                    j += 1;
132                }
133                '{' if !in_string && !in_char && !in_single_comment && !in_multi_comment => {
134                    brace_count += 1;
135                }
136                '}' if !in_string && !in_char && !in_single_comment && !in_multi_comment => {
137                    brace_count -= 1;
138                    if brace_count == 0 {
139                        let is_class_or_struct = lines[start_idx].contains("class ")
140                            || lines[start_idx].contains("struct ");
141                        if is_class_or_struct {
142                            let remaining_on_line = &line[j + 1..];
143                            if remaining_on_line.trim().starts_with(';') {
144                                return (start_line, result_lines);
145                            } else if i + 1 < lines.len() {
146                                let next_line = lines[i + 1];
147                                if next_line.trim().starts_with(';') {
148                                    result_lines.push(format!("     {:4}: {}", i + 2, next_line));
149                                }
150                            }
151                        }
152                        return (start_line, result_lines);
153                    }
154                }
155                _ => {}
156            }
157            j += 1;
158        }
159
160        in_single_comment = false;
161
162        if result_lines.len() > 200 {
163            result_lines.push("   ...  : (method too long, truncated)".to_string());
164            break;
165        }
166    }
167
168    (start_line, result_lines)
169}
170
171pub fn is_potential_definition(line: &Line, query: &str) -> bool {
172    let line_text = &line.line;
173    let line_lower = line_text.to_lowercase();
174    let query_lower = query.to_lowercase();
175
176    let is_constructor = if let Some(colon_pos) = query.rfind("::") {
177        let class_part = &query[..colon_pos];
178        let method_part = &query[colon_pos + 2..];
179        let class_name = class_part.split("::").last().unwrap_or(class_part);
180        if class_name != method_part {
181            return false;
182        }
183        line_text.contains(&format!("::{}(", method_part))
184            || (line_text
185                .trim_start()
186                .starts_with(&format!("{}(", method_part))
187                && !line_text.contains("return"))
188    } else {
189        false
190    };
191
192    let contains_query =
193        line_text.contains(query) || line_lower.contains(&query_lower) || is_constructor;
194
195    if contains_query {
196        let looks_like_definition = line_text.contains("{")
197            || line_text.trim_end().ends_with(';')
198            || line_text.contains("=")
199            || line_text.contains("class ")
200            || line_text.contains("struct ")
201            || line_text.contains("interface ")
202            || is_constructor
203            || (line_text.contains("::")
204                && (line_text.contains("(")
205                    || line_text.contains("already_AddRefed")
206                    || line_text.contains("RefPtr")
207                    || line_text.contains("nsCOMPtr")));
208
209        looks_like_definition
210    } else {
211        false
212    }
213}
214
215pub fn searchfox_url_repo(repo: &str) -> &str {
216    // The firefox-* renames only exist on searchfox.org; a server behind
217    // SEARCHFOX_BASE_URL serves trees under their configured names verbatim.
218    if std::env::var("SEARCHFOX_BASE_URL").is_ok() {
219        return repo;
220    }
221    match repo {
222        "mozilla-central" => "firefox-main",
223        "mozilla-beta" => "firefox-beta",
224        "mozilla-release" => "firefox-release",
225        "mozilla-esr128" => "firefox-esr128",
226        "mozilla-esr140" => "firefox-esr140",
227        _ => repo,
228    }
229}