searchfox-lib 0.10.7

Library for searchfox.org API access
Documentation
use crate::types::Line;

pub fn is_mozilla_repository() -> bool {
    std::path::Path::new("./mach").exists()
}

pub fn read_local_file(file_path: &str) -> Option<String> {
    if let Ok(content) = std::fs::read_to_string(file_path) {
        return Some(content);
    }
    if let Ok(content) = std::fs::read_to_string(format!("./{file_path}")) {
        return Some(content);
    }
    None
}

pub fn find_symbol_in_local_content(
    content: &str,
    expected_line: usize,
    symbol: &str,
) -> Option<usize> {
    let lines: Vec<&str> = content.lines().collect();

    if expected_line > 0 && expected_line <= lines.len() {
        let line_idx = expected_line - 1;
        if lines[line_idx].contains(symbol)
            || (symbol.contains("::")
                && lines[line_idx].contains(symbol.split("::").last().unwrap_or("")))
        {
            return Some(expected_line);
        }
    }

    let search_range = 50;
    let start = expected_line.saturating_sub(search_range);
    let end = std::cmp::min(expected_line + search_range, lines.len());

    for i in start..end {
        if i < lines.len() {
            let line = lines[i];
            if (line.contains(symbol)
                || (symbol.contains("::")
                    && line.contains(symbol.split("::").last().unwrap_or(""))))
                && (line.contains("::") || line.contains("(") || line.contains("="))
            {
                return Some(i + 1);
            }
        }
    }

    None
}

pub fn extract_complete_method(lines: &[&str], start_line: usize) -> (usize, Vec<String>) {
    let start_idx = start_line.saturating_sub(1);
    if start_idx >= lines.len() {
        return (
            start_line,
            vec![lines.get(start_idx).unwrap_or(&"").to_string()],
        );
    }

    let start_line_content = lines[start_idx];

    let looks_like_function = (start_line_content.contains("(")
        && (start_line_content.contains("{")
            || start_line_content.trim_end().ends_with(")")
            || start_line_content.trim_end().ends_with(";")
            || start_line_content.contains("::")
            || start_line_content.trim_start().starts_with("fn ")
            || start_line_content.contains("function ")))
        || start_line_content.contains("class ")
        || start_line_content.contains("struct ")
        || start_line_content.contains("interface ");

    if !looks_like_function {
        let mut found_function_pattern = false;
        for i in 0..=5.min(lines.len().saturating_sub(start_idx + 1)) {
            if let Some(line) = lines.get(start_idx + i) {
                if line.contains("{") || line.trim_start().starts_with(":") {
                    found_function_pattern = true;
                    break;
                }
            }
        }

        if !found_function_pattern {
            let context_start = start_idx.saturating_sub(5);
            let context_end = std::cmp::min(start_idx + 5, lines.len());
            let context_lines: Vec<String> = lines[context_start..context_end]
                .iter()
                .enumerate()
                .map(|(i, line)| {
                    let line_num = context_start + i + 1;
                    let marker = if line_num == start_line { ">>>" } else { "   " };
                    format!("{marker} {line_num:4}: {line}")
                })
                .collect();
            return (start_line, context_lines);
        }
    }

    if start_line_content.trim_end().ends_with(';')
        && !(start_line_content.contains("class ") || start_line_content.contains("struct "))
    {
        return (
            start_line,
            vec![format!(">>> {:4}: {}", start_line, start_line_content)],
        );
    }

    let mut found_opening_brace = start_line_content.contains('{');

    if !found_opening_brace {
        for (i, line) in lines.iter().enumerate().skip(start_idx + 1) {
            if line.contains('{') {
                found_opening_brace = true;
                break;
            }
            if i > start_idx + 25
                || (line.trim().is_empty() && i > start_idx + 5)
                || (line.contains("::")
                    && line.contains("(")
                    && !line.trim_start().starts_with("//")
                    && !line.contains("mId")
                    && !line.contains("m"))
            {
                break;
            }
        }
    }

    if !found_opening_brace {
        return (
            start_line,
            vec![format!(">>> {:4}: {}", start_line, start_line_content)],
        );
    }

    let mut result_lines = Vec::new();
    let mut brace_count = 0;
    let mut in_string = false;
    let mut in_char = false;
    let mut escaped = false;
    let mut in_single_comment = false;
    let mut in_multi_comment = false;

    for (i, line) in lines.iter().enumerate().skip(start_idx) {
        let line_num = i + 1;
        let marker = if line_num == start_line { ">>>" } else { "   " };
        result_lines.push(format!("{marker} {line_num:4}: {line}"));

        let chars: Vec<char> = line.chars().collect();
        let mut j = 0;
        while j < chars.len() {
            let ch = chars[j];
            let next_ch = chars.get(j + 1).copied();

            if escaped {
                escaped = false;
                j += 1;
                continue;
            }

            match ch {
                '\\' if in_string || in_char => escaped = true,
                '"' if !in_char && !in_single_comment && !in_multi_comment => {
                    in_string = !in_string
                }
                '\'' if !in_string && !in_single_comment && !in_multi_comment => in_char = !in_char,
                '/' if !in_string && !in_char && !in_single_comment && !in_multi_comment => {
                    if next_ch == Some('/') {
                        in_single_comment = true;
                        j += 1;
                    } else if next_ch == Some('*') {
                        in_multi_comment = true;
                        j += 1;
                    }
                }
                '*' if in_multi_comment && next_ch == Some('/') => {
                    in_multi_comment = false;
                    j += 1;
                }
                '{' if !in_string && !in_char && !in_single_comment && !in_multi_comment => {
                    brace_count += 1;
                }
                '}' if !in_string && !in_char && !in_single_comment && !in_multi_comment => {
                    brace_count -= 1;
                    if brace_count == 0 {
                        let is_class_or_struct = lines[start_idx].contains("class ")
                            || lines[start_idx].contains("struct ");
                        if is_class_or_struct {
                            let remaining_on_line = &line[j + 1..];
                            if remaining_on_line.trim().starts_with(';') {
                                return (start_line, result_lines);
                            } else if i + 1 < lines.len() {
                                let next_line = lines[i + 1];
                                if next_line.trim().starts_with(';') {
                                    result_lines.push(format!("     {:4}: {}", i + 2, next_line));
                                }
                            }
                        }
                        return (start_line, result_lines);
                    }
                }
                _ => {}
            }
            j += 1;
        }

        in_single_comment = false;

        if result_lines.len() > 200 {
            result_lines.push("   ...  : (method too long, truncated)".to_string());
            break;
        }
    }

    (start_line, result_lines)
}

pub fn is_potential_definition(line: &Line, query: &str) -> bool {
    let line_text = &line.line;
    let line_lower = line_text.to_lowercase();
    let query_lower = query.to_lowercase();

    let is_constructor = if let Some(colon_pos) = query.rfind("::") {
        let class_part = &query[..colon_pos];
        let method_part = &query[colon_pos + 2..];
        let class_name = class_part.split("::").last().unwrap_or(class_part);
        if class_name != method_part {
            return false;
        }
        line_text.contains(&format!("::{}(", method_part))
            || (line_text
                .trim_start()
                .starts_with(&format!("{}(", method_part))
                && !line_text.contains("return"))
    } else {
        false
    };

    let contains_query =
        line_text.contains(query) || line_lower.contains(&query_lower) || is_constructor;

    if contains_query {
        let looks_like_definition = line_text.contains("{")
            || line_text.trim_end().ends_with(';')
            || line_text.contains("=")
            || line_text.contains("class ")
            || line_text.contains("struct ")
            || line_text.contains("interface ")
            || is_constructor
            || (line_text.contains("::")
                && (line_text.contains("(")
                    || line_text.contains("already_AddRefed")
                    || line_text.contains("RefPtr")
                    || line_text.contains("nsCOMPtr")));

        looks_like_definition
    } else {
        false
    }
}

pub fn searchfox_url_repo(repo: &str) -> &str {
    match repo {
        "mozilla-central" => "firefox-main",
        "autoland" => "firefox-autoland",
        "mozilla-beta" => "firefox-beta",
        "mozilla-release" => "firefox-release",
        "mozilla-esr128" => "firefox-esr128",
        "mozilla-esr140" => "firefox-esr140",
        _ => repo,
    }
}