1use std::{collections::BTreeMap, path::PathBuf};
2
3use chrono::{DateTime, Local};
4use foundry_tui_config::{ActionId, CustomTemplate};
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7pub enum Tab {
8 Build,
9 Test,
10 Script,
11 Anvil,
12 Cast,
13 Verify,
14 Custom,
15 Logs,
16 History,
17}
18
19impl Tab {
20 pub const ALL: [Tab; 9] = [
21 Tab::Build,
22 Tab::Test,
23 Tab::Script,
24 Tab::Anvil,
25 Tab::Cast,
26 Tab::Verify,
27 Tab::Custom,
28 Tab::Logs,
29 Tab::History,
30 ];
31
32 pub fn title(self) -> &'static str {
33 match self {
34 Tab::Build => "Build",
35 Tab::Test => "Test",
36 Tab::Script => "Script",
37 Tab::Anvil => "Anvil",
38 Tab::Cast => "Cast",
39 Tab::Verify => "Verify",
40 Tab::Custom => "Builder",
41 Tab::Logs => "Logs",
42 Tab::History => "History",
43 }
44 }
45}
46
47#[derive(Debug, Clone, Copy, PartialEq, Eq)]
48pub enum SectionFocus {
49 MainPanel,
50 JobsPanel,
51 LogsPanel,
52 AnvilInstancesPanel,
53 AnvilInstanceLogsPanel,
54}
55
56impl SectionFocus {
57 pub fn label(self) -> &'static str {
58 match self {
59 SectionFocus::MainPanel => "Main",
60 SectionFocus::JobsPanel => "Jobs",
61 SectionFocus::LogsPanel => "Logs",
62 SectionFocus::AnvilInstancesPanel => "Anvil Instances",
63 SectionFocus::AnvilInstanceLogsPanel => "Anvil Logs",
64 }
65 }
66}
67
68#[derive(Debug, Clone, Copy, PartialEq, Eq)]
69pub enum JobStatus {
70 Running,
71 Success,
72 Failed,
73 Cancelled,
74}
75
76impl JobStatus {
77 pub fn label(self) -> &'static str {
78 match self {
79 JobStatus::Running => "running",
80 JobStatus::Success => "success",
81 JobStatus::Failed => "failed",
82 JobStatus::Cancelled => "cancelled",
83 }
84 }
85}
86
87#[derive(Debug, Clone)]
88pub struct JobRecord {
89 pub id: u64,
90 pub name: String,
91 pub commandline: String,
92 pub status: JobStatus,
93 pub started_at: DateTime<Local>,
94 pub finished_at: Option<DateTime<Local>>,
95 pub duration_ms: Option<u128>,
96 pub status_code: Option<i32>,
97}
98
99#[derive(Debug, Clone, Copy, PartialEq, Eq)]
100pub enum LogStream {
101 System,
102 Stdout,
103 Stderr,
104}
105
106#[derive(Debug, Clone)]
107pub struct LogLine {
108 pub ts: DateTime<Local>,
109 pub job_id: Option<u64>,
110 pub stream: LogStream,
111 pub message: String,
112}
113
114#[derive(Debug, Clone, Copy, PartialEq, Eq)]
115pub enum AnvilInstanceStatus {
116 Starting,
117 Running,
118 Failed,
119 Stopped,
120}
121
122impl AnvilInstanceStatus {
123 pub fn label(self) -> &'static str {
124 match self {
125 AnvilInstanceStatus::Starting => "starting",
126 AnvilInstanceStatus::Running => "running",
127 AnvilInstanceStatus::Failed => "failed",
128 AnvilInstanceStatus::Stopped => "stopped",
129 }
130 }
131}
132
133#[derive(Debug, Clone)]
134pub struct AnvilInstance {
135 pub job_id: u64,
136 pub name: String,
137 pub port: u16,
138 pub fork_url: Option<String>,
139 pub status: AnvilInstanceStatus,
140 pub logs: Vec<LogLine>,
141}
142
143#[derive(Debug, Clone, Copy, PartialEq, Eq)]
144pub enum AnvilPromptField {
145 Name,
146 Port,
147 ForkUrl,
148 ExtraFlags,
149}
150
151impl AnvilPromptField {
152 pub(crate) fn next(self) -> Self {
153 match self {
154 AnvilPromptField::Name => AnvilPromptField::Port,
155 AnvilPromptField::Port => AnvilPromptField::ForkUrl,
156 AnvilPromptField::ForkUrl => AnvilPromptField::ExtraFlags,
157 AnvilPromptField::ExtraFlags => AnvilPromptField::Name,
158 }
159 }
160
161 pub(crate) fn prev(self) -> Self {
162 match self {
163 AnvilPromptField::Name => AnvilPromptField::ExtraFlags,
164 AnvilPromptField::Port => AnvilPromptField::Name,
165 AnvilPromptField::ForkUrl => AnvilPromptField::Port,
166 AnvilPromptField::ExtraFlags => AnvilPromptField::ForkUrl,
167 }
168 }
169}
170
171#[derive(Debug, Clone)]
172pub struct AnvilLaunchPrompt {
173 pub name: String,
174 pub port: String,
175 pub fork_url: String,
176 pub extra_flags: String,
177 pub focus: AnvilPromptField,
178 pub error: Option<String>,
179}
180
181#[derive(Debug, Clone, Copy, PartialEq, Eq)]
182pub enum CustomModalStep {
183 TemplatePicker,
184 Editor,
185 Preview,
186}
187
188#[derive(Debug, Clone)]
189pub struct CustomCommandDraft {
190 pub template: CustomTemplate,
191 pub rpc_preset: String,
192 pub raw_args: String,
193 pub param_values: BTreeMap<String, String>,
194 pub merged_args: Vec<String>,
195 pub resolved_args: Vec<String>,
196 pub display_args: Vec<String>,
197 pub display_commandline: String,
198 pub rpc_url: Option<String>,
199}
200
201#[derive(Debug, Clone)]
202pub struct CustomCommandModal {
203 pub step: CustomModalStep,
204 pub picker_index: usize,
205 pub paste_mode: bool,
206 pub paste_input: String,
207 pub editor_index: usize,
208 pub draft: Option<CustomCommandDraft>,
209 pub error: Option<String>,
210}
211
212#[derive(Debug, Clone, Copy, PartialEq, Eq)]
213pub enum LogTextMode {
214 Horizontal,
215 Wrapped,
216}
217
218impl LogTextMode {
219 pub fn toggle(self) -> Self {
220 match self {
221 LogTextMode::Horizontal => LogTextMode::Wrapped,
222 LogTextMode::Wrapped => LogTextMode::Horizontal,
223 }
224 }
225
226 pub fn short_label(self) -> &'static str {
227 match self {
228 LogTextMode::Horizontal => "H",
229 LogTextMode::Wrapped => "W",
230 }
231 }
232
233 pub fn label(self) -> &'static str {
234 match self {
235 LogTextMode::Horizontal => "horizontal",
236 LogTextMode::Wrapped => "wrapped",
237 }
238 }
239}
240
241#[derive(Debug, Clone)]
242pub struct AppModel {
243 pub active_tab: Tab,
244 pub tabs: Vec<Tab>,
245 pub jobs: BTreeMap<u64, JobRecord>,
246 pub history: Vec<JobRecord>,
247 pub logs: Vec<LogLine>,
248 pub focused_section: SectionFocus,
249 pub palette_open: bool,
250 pub palette_index: usize,
251 pub palette_actions: Vec<ActionId>,
252 pub show_build_onboarding: bool,
253 pub mouse_mode_enabled: bool,
254 pub log_text_mode: LogTextMode,
255 pub main_scroll: usize,
256 pub jobs_scroll: usize,
257 pub logs_scroll: usize,
258 pub anvil_logs_scroll: usize,
259 pub logs_hscroll: usize,
260 pub anvil_logs_hscroll: usize,
261 pub notification: Option<String>,
262 pub key_hints: BTreeMap<ActionId, String>,
263 pub project_root: PathBuf,
264 pub config_path: PathBuf,
265 pub project_sol_files: Vec<String>,
266 pub project_has_foundry_toml: bool,
267 pub project_has_remappings: bool,
268 pub project_indexed_at: DateTime<Local>,
269 pub active_rpc_preset: Option<String>,
270 pub active_rpc_chain: Option<String>,
271 pub active_rpc_url: Option<String>,
272 pub custom_templates: Vec<CustomTemplate>,
273 pub custom_template_index: usize,
274 pub custom_templates_global_path: PathBuf,
275 pub custom_templates_project_path: PathBuf,
276 pub custom_modal: Option<CustomCommandModal>,
277 pub anvil_instances: Vec<AnvilInstance>,
278 pub selected_anvil_index: usize,
279 pub anvil_prompt: Option<AnvilLaunchPrompt>,
280 pub should_quit: bool,
281 pub launched_at: DateTime<Local>,
282}
283
284impl AppModel {
285 pub fn active_tab_index(&self) -> usize {
286 self.tabs
287 .iter()
288 .position(|tab| *tab == self.active_tab)
289 .unwrap_or(0)
290 }
291}