1use crate::tools::search_tools;
7use std::path::Path;
8
9pub fn word_at_position(content: &str, line: u32, col: u32) -> Option<String> {
14 let lines: Vec<&str> = content.lines().collect();
15 let row = lines.get(line.saturating_sub(1) as usize)?;
16 let col0 = col.saturating_sub(1) as usize;
17 if col0 >= row.len() {
18 return None;
19 }
20
21 let is_ident = |c: char| c.is_alphanumeric() || c == '_';
22
23 let start = row[..col0]
25 .rfind(|c: char| !is_ident(c))
26 .map(|i| i + 1)
27 .unwrap_or(0);
28
29 let end = row[col0..]
31 .find(|c: char| !is_ident(c))
32 .map(|i| col0 + i)
33 .unwrap_or(row.len());
34
35 let word = &row[start..end];
36 if word.is_empty() {
37 None
38 } else {
39 Some(word.to_string())
40 }
41}
42
43pub fn go_to_definition_grep(workspace: &Path, file: &Path, line: u32, col: u32) -> String {
47 let content = match std::fs::read_to_string(workspace.join(file)) {
48 Ok(c) => c,
49 Err(e) => return format!("Cannot read '{}': {}", file.display(), e),
50 };
51 let word = match word_at_position(&content, line, col) {
52 Some(w) => w,
53 None => return format!("No identifier at {}:{}", line, col),
54 };
55
56 let escaped = regex::escape(&word);
58 let pattern = format!(
59 r"(?:fn|struct|enum|trait|impl|class|def|interface|type|const|let|var|function)\s+{}\b",
60 escaped
61 );
62
63 match search_tools::search_codebase(&pattern, workspace) {
64 Ok(results) if !results.starts_with("No matches") => {
65 format!("Definition candidates for '{}' (grep):\n{}", word, results)
66 }
67 _ => format!(
68 "No definition found for '{}'. \
69 Tip: use --lsp for accurate jump-to-definition.",
70 word
71 ),
72 }
73}
74
75pub fn find_references_grep(workspace: &Path, file: &Path, line: u32, col: u32) -> String {
77 let content = match std::fs::read_to_string(workspace.join(file)) {
78 Ok(c) => c,
79 Err(e) => return format!("Cannot read '{}': {}", file.display(), e),
80 };
81 let word = match word_at_position(&content, line, col) {
82 Some(w) => w,
83 None => return format!("No identifier at {}:{}", line, col),
84 };
85
86 let pattern = format!(r"\b{}\b", regex::escape(&word));
87 match search_tools::search_codebase(&pattern, workspace) {
88 Ok(results) => format!(
89 "References to '{}' (grep — use --lsp for accurate results):\n{}",
90 word, results
91 ),
92 Err(e) => format!("Search error: {}", e),
93 }
94}
95
96pub fn hover_grep(workspace: &Path, file: &Path, line: u32, col: u32) -> String {
101 let content = match std::fs::read_to_string(workspace.join(file)) {
102 Ok(c) => c,
103 Err(e) => return format!("Cannot read '{}': {}", file.display(), e),
104 };
105
106 let word = word_at_position(&content, line, col)
107 .unwrap_or_else(|| format!("position {}:{}", line, col));
108
109 let lines: Vec<&str> = content.lines().collect();
110 let target = line.saturating_sub(1) as usize;
111 let start = target.saturating_sub(2);
112 let end = (target + 3).min(lines.len());
113
114 let mut out = format!(
115 "Context for '{}' at {}:{} (grep fallback):\n\n",
116 word,
117 file.display(),
118 line
119 );
120 for (i, text) in lines[start..end].iter().enumerate() {
121 let lnum = start + i + 1;
122 let arrow = if lnum == line as usize { "▶" } else { " " };
123 out.push_str(&format!("{} {:4} │ {}\n", arrow, lnum, text));
124 }
125
126 let escaped = regex::escape(&word);
128 let def_pattern = format!(
129 r"(?:fn|struct|enum|trait|impl|class|def|interface|type|const)\s+{}\b",
130 escaped
131 );
132 if let Ok(defs) = search_tools::search_codebase(&def_pattern, workspace) {
133 if !defs.starts_with("No matches") {
134 out.push_str(&format!("\nDefinition candidates:\n{}\n", defs));
135 }
136 }
137
138 out
139}