use crate::tools::search_tools;
use std::path::Path;
pub fn word_at_position(content: &str, line: u32, col: u32) -> Option<String> {
let lines: Vec<&str> = content.lines().collect();
let row = lines.get(line.saturating_sub(1) as usize)?;
let col0 = col.saturating_sub(1) as usize;
if col0 >= row.len() {
return None;
}
let is_ident = |c: char| c.is_alphanumeric() || c == '_';
let start = row[..col0]
.rfind(|c: char| !is_ident(c))
.map(|i| i + 1)
.unwrap_or(0);
let end = row[col0..]
.find(|c: char| !is_ident(c))
.map(|i| col0 + i)
.unwrap_or(row.len());
let word = &row[start..end];
if word.is_empty() {
None
} else {
Some(word.to_string())
}
}
pub fn go_to_definition_grep(workspace: &Path, file: &Path, line: u32, col: u32) -> String {
let content = match std::fs::read_to_string(workspace.join(file)) {
Ok(c) => c,
Err(e) => return format!("Cannot read '{}': {}", file.display(), e),
};
let word = match word_at_position(&content, line, col) {
Some(w) => w,
None => return format!("No identifier at {}:{}", line, col),
};
let escaped = regex::escape(&word);
let pattern = format!(
r"(?:fn|struct|enum|trait|impl|class|def|interface|type|const|let|var|function)\s+{}\b",
escaped
);
match search_tools::search_codebase(&pattern, workspace) {
Ok(results) if !results.starts_with("No matches") => {
format!("Definition candidates for '{}' (grep):\n{}", word, results)
}
_ => format!(
"No definition found for '{}'. \
Tip: use --lsp for accurate jump-to-definition.",
word
),
}
}
pub fn find_references_grep(workspace: &Path, file: &Path, line: u32, col: u32) -> String {
let content = match std::fs::read_to_string(workspace.join(file)) {
Ok(c) => c,
Err(e) => return format!("Cannot read '{}': {}", file.display(), e),
};
let word = match word_at_position(&content, line, col) {
Some(w) => w,
None => return format!("No identifier at {}:{}", line, col),
};
let pattern = format!(r"\b{}\b", regex::escape(&word));
match search_tools::search_codebase(&pattern, workspace) {
Ok(results) => format!(
"References to '{}' (grep — use --lsp for accurate results):\n{}",
word, results
),
Err(e) => format!("Search error: {}", e),
}
}
pub fn hover_grep(workspace: &Path, file: &Path, line: u32, col: u32) -> String {
let content = match std::fs::read_to_string(workspace.join(file)) {
Ok(c) => c,
Err(e) => return format!("Cannot read '{}': {}", file.display(), e),
};
let word = word_at_position(&content, line, col)
.unwrap_or_else(|| format!("position {}:{}", line, col));
let lines: Vec<&str> = content.lines().collect();
let target = line.saturating_sub(1) as usize;
let start = target.saturating_sub(2);
let end = (target + 3).min(lines.len());
let mut out = format!(
"Context for '{}' at {}:{} (grep fallback):\n\n",
word,
file.display(),
line
);
for (i, text) in lines[start..end].iter().enumerate() {
let lnum = start + i + 1;
let arrow = if lnum == line as usize { "▶" } else { " " };
out.push_str(&format!("{} {:4} │ {}\n", arrow, lnum, text));
}
let escaped = regex::escape(&word);
let def_pattern = format!(
r"(?:fn|struct|enum|trait|impl|class|def|interface|type|const)\s+{}\b",
escaped
);
if let Ok(defs) = search_tools::search_codebase(&def_pattern, workspace) {
if !defs.starts_with("No matches") {
out.push_str(&format!("\nDefinition candidates:\n{}\n", defs));
}
}
out
}