deepseek_rust_cli/tui/
app.rs1use std::sync::Arc;
2use std::time::Instant;
3
4use crate::api::types::TokenUsage;
5
6#[derive(Debug, Clone, Copy)]
7pub struct TerminalSize {
8 pub width: u16,
9 pub height: u16,
10}
11
12pub fn load_global_history() -> Vec<String> {
13 let path = std::path::PathBuf::from(".deep/input_history.json");
14 if let Ok(content) = std::fs::read_to_string(path) {
15 if let Ok(history) = serde_json::from_str::<Vec<String>>(&content) {
16 return history;
17 }
18 }
19 Vec::new()
20}
21
22pub fn save_global_history(history: &[String]) {
23 let path = std::path::PathBuf::from(".deep/input_history.json");
24 if let Some(parent) = path.parent() {
25 let _ = std::fs::create_dir_all(parent);
26 }
27 if let Ok(json) = serde_json::to_string_pretty(history) {
28 let _ = std::fs::write(path, json);
29 }
30}
31
32pub struct App {
33 pub input: String,
34 pub cursor_pos: usize,
36 pub awaiting_approval: bool,
37 pub spinner_frame: usize,
38 pub current_task: Option<String>,
39 pub task_start_time: Option<Instant>,
41 pub job_start_time: Option<Instant>,
43 pub cwd: String,
44 pub model: String,
45 pub history: Vec<String>,
46 pub history_index: Option<usize>,
47 pub footer_height: u16,
49 pub token_usage: TokenUsage,
50 pub aborted: bool,
52 pub queued_commands: Vec<String>,
55 pub log_x: u16,
56 pub log_y: u16,
57 pub reasoning_started: bool,
58 pub content_started: bool,
59 pub is_path_traversal_warning: bool,
60 pub terminal_size: Arc<std::sync::RwLock<TerminalSize>>,
61 pub output_buffer: String,
62 pub last_key_time: Option<Instant>,
63}
64
65impl Default for App {
66 fn default() -> Self {
67 Self::new()
68 }
69}
70
71impl App {
72 pub fn new() -> Self {
73 let history = load_global_history();
74 let (width, height) = crossterm::terminal::size().unwrap_or((80, 24));
75 Self {
76 input: String::new(),
77 cursor_pos: 0,
78 awaiting_approval: false,
79 spinner_frame: 0,
80 current_task: None,
81 task_start_time: None,
82 job_start_time: None,
83 cwd: std::env::current_dir()
84 .map(|p| p.display().to_string())
85 .unwrap_or_else(|_| ".".to_string()),
86 model: String::from("unknown"),
87 history,
88 history_index: None,
89 footer_height: 4, token_usage: TokenUsage::default(),
91 aborted: false,
92 queued_commands: Vec::new(),
93 log_x: 0,
94 log_y: 0,
95 reasoning_started: false,
96 content_started: false,
97 is_path_traversal_warning: false,
98 terminal_size: Arc::new(std::sync::RwLock::new(TerminalSize { width, height })),
99 output_buffer: String::new(),
100 last_key_time: None,
101 }
102 }
103
104 pub fn next_history(&mut self) {
105 if self.history.is_empty() {
106 return;
107 }
108 let next_index = match self.history_index {
109 Some(i) => {
110 if i > 0 {
111 Some(i - 1)
112 } else {
113 Some(0)
114 }
115 }
116 None => Some(self.history.len().saturating_sub(1)),
117 };
118 if let Some(idx) = next_index {
119 self.history_index = Some(idx);
120 self.input = self.history[idx].clone();
121 self.cursor_pos = self.input.len();
122 }
123 }
124
125 pub fn prev_history(&mut self) {
126 if self.history.is_empty() {
127 return;
128 }
129 let next_index = match self.history_index {
130 Some(i) => {
131 if i < self.history.len().saturating_sub(1) {
132 Some(i + 1)
133 } else {
134 self.input.clear();
135 self.cursor_pos = 0;
136 None
137 }
138 }
139 None => None,
140 };
141 self.history_index = next_index;
142 if let Some(idx) = self.history_index {
143 self.input = self.history[idx].clone();
144 self.cursor_pos = self.input.len();
145 }
146 }
147
148 pub fn start_task(&mut self, task: String) {
149 if self.job_start_time.is_none() {
151 self.job_start_time = Some(Instant::now());
152 }
153 if self.current_task.as_ref() != Some(&task) {
154 self.current_task = Some(task);
155 self.task_start_time = Some(Instant::now());
156 }
157 }
158
159 pub fn finish_task(&mut self) {
160 self.current_task = None;
161 self.task_start_time = None;
162 self.job_start_time = None;
163 self.awaiting_approval = false;
164 self.is_path_traversal_warning = false;
165 self.aborted = false;
166 if !self.queued_commands.is_empty() {
168 self.queued_commands.remove(0);
169 }
170 }
171
172 pub fn tick(&mut self) {
173 self.spinner_frame = self.spinner_frame.wrapping_add(1);
174 }
175
176 pub fn total_tokens(&self) -> u64 {
178 self.token_usage.prompt_tokens + self.token_usage.completion_tokens
179 }
180}