Skip to main content

foundry_tui_app/
model.rs

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}