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 is_constructor = if let Some(colon_pos) = query.rfind("::") {
228 let class_part = &query[..colon_pos];
229 let method_part = &query[colon_pos + 2..];
230 let class_name = class_part.split("::").last().unwrap_or(class_part);
231 if class_name != method_part {
232 return false;
233 }
234 line_text.contains(&format!("::{}(", method_part))
235 || (line_text
236 .trim_start()
237 .starts_with(&format!("{}(", method_part))
238 && !line_text.contains("return"))
239 } else {
240 false
241 };
242
243 let contains_query =
244 line_text.contains(query) || line_lower.contains(&query_lower) || is_constructor;
245
246 if contains_query {
247 let looks_like_definition = line_text.contains("{")
248 || line_text.trim_end().ends_with(';')
249 || line_text.contains("=")
250 || line_text.contains("class ")
251 || line_text.contains("struct ")
252 || line_text.contains("interface ")
253 || is_constructor
254 || (line_text.contains("::")
255 && (line_text.contains("(")
256 || line_text.contains("already_AddRefed")
257 || line_text.contains("RefPtr")
258 || line_text.contains("nsCOMPtr")));
259
260 looks_like_definition
261 } else {
262 false
263 }
264}
265
266pub fn get_github_raw_url(repo: &str, file_path: &str) -> String {
267 let github_repo = match repo {
268 "comm-central" => "mozilla/releases-comm-central",
269 _ => "mozilla/firefox",
270 };
271
272 let branch = match repo {
273 "mozilla-central" => "main",
274 "autoland" => "autoland",
275 "mozilla-beta" => "beta",
276 "mozilla-release" => "release",
277 "mozilla-esr115" => "esr115",
278 "mozilla-esr128" => "esr128",
279 "mozilla-esr140" => "esr140",
280 "comm-central" => "main",
281 _ => "main",
282 };
283
284 format!("https://raw.githubusercontent.com/{github_repo}/{branch}/{file_path}")
285}