deepseek_rust_cli/agent/
mentions.rs1use std::{fs, sync::OnceLock};
2
3use regex::Regex;
4
5static MENTION_RE: OnceLock<Regex> = OnceLock::new();
6
7pub fn process_mentions(text: &str) -> String {
8 let mention_re = MENTION_RE.get_or_init(|| Regex::new(r"@([^\n@\s]+)").unwrap());
9
10 let mut result = String::new();
11 let mut last_end = 0;
12
13 for cap in mention_re.captures_iter(text) {
14 let full_match = cap.get(0).unwrap();
15 let path_str = cap[1].trim();
16
17 result.push_str(&text[last_end..full_match.start()]);
19
20 let validated_path = match crate::tools::base::validate_path(path_str) {
22 Ok(p) => p,
23 Err(_) => {
24 result.push_str(full_match.as_str());
25 last_end = full_match.end();
26 continue;
27 }
28 };
29
30 if validated_path.exists() && validated_path.is_file() {
31 if let Ok(content) = fs::read_to_string(&validated_path) {
32 let trimmed = if content.len() > 50000 {
33 let truncate_at = content
34 .char_indices()
35 .nth(50000)
36 .map(|(i, _)| i)
37 .unwrap_or(content.len());
38 format!("{}... (truncated)", &content[..truncate_at])
39 } else {
40 content
41 };
42 let replacement = format!("@{} (File Content):\n```\n{}\n```", path_str, trimmed);
43 result.push_str(&replacement);
44 } else {
45 result.push_str(full_match.as_str());
46 }
47 } else {
48 result.push_str(full_match.as_str());
49 }
50
51 last_end = full_match.end();
52 }
53
54 result.push_str(&text[last_end..]);
56 result
57}
58
59#[cfg(test)]
60mod tests {
61 use std::fs;
62
63 use tempfile::TempDir;
64
65 use super::*;
66
67 #[test]
68 fn test_process_mentions_basic() {
69 let dir = TempDir::new_in(".").unwrap();
70 let file_path = dir.path().join("mention.txt");
71 fs::write(&file_path, "file content").unwrap();
72
73 let path_str = file_path.to_str().unwrap();
74 let input = format!("check this file @{}", path_str);
75 let processed = process_mentions(&input);
76
77 assert!(processed.contains("file content"));
78 assert!(processed.contains("check this file"));
79 }
80}