deepseek_rust_cli/tui/
utils.rs1use crate::tui::colorizer::CodeLang;
2
3pub fn truncate_str(s: &str, max_width: usize) -> String {
4 if max_width == 0 {
5 return String::new();
6 }
7 if s.chars().count() > max_width {
8 let truncated: String = s.chars().take(max_width.saturating_sub(1)).collect();
9 format!("{}…", truncated)
10 } else {
11 s.to_string()
12 }
13}
14
15static ANSI_RE: std::sync::OnceLock<regex::Regex> = std::sync::OnceLock::new();
16
17fn ansi_re() -> &'static regex::Regex {
18 ANSI_RE.get_or_init(|| regex::Regex::new("\x1b\\[[0-9;]*m").expect("valid ansi regex"))
19}
20
21pub fn truncate_ansi_str(s: &str, max_width: usize) -> String {
22 if max_width == 0 {
23 return String::new();
24 }
25 let re = ansi_re();
26 let mut visible = 0usize;
27 let mut result = String::new();
28 let mut remaining = s;
29
30 while !remaining.is_empty() {
31 if let Some(m) = re.find(remaining) {
32 if m.start() == 0 {
33 result.push_str(m.as_str());
34 remaining = &remaining[m.end()..];
35 continue;
36 }
37 let text = &remaining[..m.start()];
39 for ch in text.chars() {
40 if visible >= max_width {
41 result.push('…');
42 return result;
43 }
44 result.push(ch);
45 visible += 1;
46 }
47 remaining = &remaining[m.start()..];
48 } else {
49 for ch in remaining.chars() {
51 if visible >= max_width {
52 result.push('…');
53 return result;
54 }
55 result.push(ch);
56 visible += 1;
57 }
58 break;
59 }
60 }
61 result
62}
63
64pub fn strip_ansi(s: &str) -> String {
65 let re = ansi_re();
66 re.replace_all(s, "").to_string()
67}
68
69pub fn format_tool_args(name: &str, args: &str) -> String {
70 if let Ok(obj) = serde_json::from_str::<serde_json::Value>(args) {
71 match name {
72 "execute_shell_command" => {
73 if let Some(cmd) = obj.get("command").and_then(|v| v.as_str()) {
74 return format!(" $ {}", cmd);
75 }
76 }
77 "read_local_file" | "write_local_file" | "delete_file" | "cleanup_file"
78 | "file_exists" | "get_file_info" => {
79 if let Some(path) = obj.get("file_path").and_then(|v| v.as_str()) {
80 return format!(" 📄 {}", path);
81 }
82 }
83 "create_directory" => {
84 if let Some(path) = obj.get("directory_path").and_then(|v| v.as_str()) {
85 return format!(" 📂 {}", path);
86 }
87 }
88 "replace_text_in_file" | "regex_replace_in_file" => {
89 if let Some(path) = obj.get("file_path").and_then(|v| v.as_str()) {
90 return format!(" 📄 {} (replace)", path);
91 }
92 }
93 "move_code_block" | "copy_file" | "copy_directory" => {
94 if let (Some(src), Some(dst)) = (
95 obj.get("source_path").and_then(|v| v.as_str()),
96 obj.get("destination_path").and_then(|v| v.as_str()),
97 ) {
98 return format!(" 📦 {} ➔ {}", src, dst);
99 }
100 }
101 "split_file" => {
102 if let Some(path) = obj.get("file_path").and_then(|v| v.as_str()) {
103 return format!(" ✂️ split: {}", path);
104 }
105 }
106 "bulk_rename" => {
107 if let Some(path) = obj.get("path").and_then(|v| v.as_str()) {
108 return format!(" 🏷️ bulk rename in: {}", path);
109 }
110 }
111 "project_wide_replace" => {
112 if let (Some(old), Some(new)) = (
113 obj.get("old_text").and_then(|v| v.as_str()),
114 obj.get("new_text").and_then(|v| v.as_str()),
115 ) {
116 return format!(" 🔍 \"{}\" ➔ \"{}\"", old, new);
117 }
118 }
119 "project_checkpoint" => {
120 if let Some(name) = obj.get("name").and_then(|v| v.as_str()) {
121 return format!(" 💾 checkpoint: {}", name);
122 }
123 }
124 "restore_checkpoint" => {
125 if let Some(name) = obj.get("checkpoint_file").and_then(|v| v.as_str()) {
126 return format!(" ⏪ restore: {}", name);
127 }
128 }
129 "summarize_project" => return " 📊 summarizing project...".to_string(),
130 "list_todo_tasks" => return " 📝 listing tasks...".to_string(),
131 "list_directory" | "tree_view" => {
132 if let Some(path) = obj.get("path").and_then(|v| v.as_str()) {
133 return format!(" 📂 {}", path);
134 }
135 }
136 "fetch_url" => {
137 if let Some(url) = obj.get("url").and_then(|v| v.as_str()) {
138 return format!(" 🌐 {}", url);
139 }
140 }
141 "diff_files" => {
142 if let (Some(f1), Some(f2)) = (
143 obj.get("file1").and_then(|v| v.as_str()),
144 obj.get("file2").and_then(|v| v.as_str()),
145 ) {
146 return format!(" 📄 {} ↔ {}", f1, f2);
147 }
148 }
149 "search_code" | "search_repos" => {
150 if let Some(q) = obj.get("query").and_then(|v| v.as_str()) {
151 return format!(" 🔍 {}", q);
152 }
153 }
154 _ => {}
155 }
156 serde_json::to_string_pretty(&obj).unwrap_or_else(|_| args.to_string())
157 } else {
158 args.to_string()
159 }
160}
161
162pub fn detect_lang_for_result(tool_name: &str, result: &str) -> CodeLang {
163 match tool_name {
164 "read_local_file" | "write_local_file" | "replace_text_in_file" => {
165 if result.trim_start().starts_with("#!/") {
166 if result.contains("python") {
167 return CodeLang::Python;
168 }
169 if result.contains("bash") || result.contains("sh") {
170 return CodeLang::Shell;
171 }
172 }
173 if result.trim_start().starts_with("<?xml")
174 || result.trim_start().starts_with("<!DOCTYPE html")
175 || result.trim_start().starts_with("<html")
176 {
177 return CodeLang::Html;
178 }
179 if result.trim_start().starts_with("{") || result.trim_start().starts_with("[") {
180 return CodeLang::Json;
181 }
182 if result.contains("fn ") && result.contains("->") {
183 return CodeLang::Rust;
184 }
185 if result.contains("def ") && result.contains("return ") {
186 return CodeLang::Python;
187 }
188 CodeLang::Generic
189 }
190 "execute_shell_command" => CodeLang::Shell,
191 "run_python_code" => CodeLang::Python,
192 "github_get_file" => CodeLang::Generic,
193 _ => CodeLang::Generic,
194 }
195}