Skip to main content

deepseek_rust_cli/tui/
utils.rs

1use 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            // Text before the escape
38            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            // No more escapes
50            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}