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