1#![allow(clippy::print_stdout, clippy::print_stderr)]
4#![cfg_attr(
6 test,
7 expect(
8 clippy::unwrap_used,
9 clippy::panic,
10 clippy::approx_constant,
11 clippy::field_reassign_with_default
12 )
13)]
14
15mod app;
16mod domain;
17mod infra;
18mod render;
19
20use domain::stored_config::Config as StoredConfig;
21
22pub use domain::{SessionField, parse_column_spec};
24
25use std::io::{self, IsTerminal};
26use std::sync::mpsc;
27use std::thread;
28use std::time::Duration;
29
30use anyhow::Result;
31use crossterm::{
32 event::{DisableMouseCapture, EnableMouseCapture},
33 execute,
34 terminal::{EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode, enable_raw_mode},
35};
36use ratatui::{Terminal, backend::CrosstermBackend};
37
38use app::App;
39use infra::{
40 DEFAULT_REFRESH_IDX, REFRESH_INTERVALS_MS, check_and_enable_new_frameworks, find_interval_idx,
41 refresh_thread, transcript_poll_thread,
42};
43
44#[derive(Debug, Default, Clone)]
46pub struct Config {
47 pub interval_ms: Option<u64>,
49 pub transcript_poll_ms: Option<u64>,
51 pub show_all: bool,
53 pub theme: Option<String>,
55 pub columns: Option<Vec<bool>>,
57 pub expert_mode: bool,
59}
60
61pub fn run(config: Config) -> Result<()> {
63 if !std::io::stdout().is_terminal() {
65 anyhow::bail!("mi6 requires an interactive terminal");
66 }
67
68 let stored_config = StoredConfig::load();
70
71 let newly_activated = check_and_enable_new_frameworks(&stored_config.framework_auto_enable);
74 if !newly_activated.is_empty() {
75 eprintln!("Auto-enabled mi6 for: {}", newly_activated.join(", "));
76 }
77
78 let refresh_interval_idx = config.interval_ms.map_or_else(
79 || {
80 stored_config
81 .refresh_interval_ms
82 .map_or(DEFAULT_REFRESH_IDX, find_interval_idx)
83 },
84 find_interval_idx,
85 );
86 let initial_interval = Duration::from_millis(REFRESH_INTERVALS_MS[refresh_interval_idx]);
87
88 let transcript_poll_interval_idx = config.transcript_poll_ms.map_or_else(
90 || {
91 stored_config
92 .transcript_poll_interval_ms
93 .map_or(DEFAULT_REFRESH_IDX, find_interval_idx)
94 },
95 find_interval_idx,
96 );
97 let initial_transcript_poll_interval =
98 Duration::from_millis(REFRESH_INTERVALS_MS[transcript_poll_interval_idx]);
99
100 let (data_tx, data_rx) = mpsc::channel();
102 let (interval_tx, interval_rx) = mpsc::channel();
103 thread::spawn(move || {
104 refresh_thread(&data_tx, &interval_rx, initial_interval);
105 });
106
107 let (transcript_poll_tx, transcript_poll_rx) = mpsc::channel();
109 let (lock_status_tx, lock_status_rx) = mpsc::channel();
110 thread::spawn(move || {
111 transcript_poll_thread(
112 &transcript_poll_rx,
113 &lock_status_tx,
114 initial_transcript_poll_interval,
115 );
116 });
117
118 let check_for_updates_enabled = stored_config.check_for_updates.unwrap_or(true);
120 let (update_tx, update_rx) = mpsc::channel::<Vec<String>>();
121 if check_for_updates_enabled {
122 thread::spawn(move || {
123 if let Ok(Some(update_info)) = mi6_cli::check_for_update() {
126 let _ = update_tx.send(update_info.notification_messages());
128 }
129 });
130 }
131
132 let first_data = data_rx.recv_timeout(Duration::from_millis(500)).ok();
135
136 enable_raw_mode()?;
138 let mut stdout = io::stdout();
139 execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?;
140 let backend = CrosstermBackend::new(stdout);
141 let mut terminal = Terminal::new(backend)?;
142
143 let mut startup_messages: Vec<String> = Vec::new();
145
146 let git_hash = env!("MI6_GIT_HASH");
148 if git_hash.is_empty() {
149 startup_messages.push(format!("Welcome to mi6 v{}", env!("CARGO_PKG_VERSION")));
150 } else {
151 startup_messages.push(format!(
152 "Welcome to mi6 v{} (0x{})",
153 env!("CARGO_PKG_VERSION"),
154 git_hash
155 ));
156 }
157
158 for name in &newly_activated {
160 startup_messages.push(format!("Auto-enabled mi6 for {name}"));
161 }
162
163 let app = App::with_first_data(
165 data_rx,
166 interval_tx,
167 Some(transcript_poll_tx),
168 Some(lock_status_rx),
169 Some(update_rx),
170 first_data,
171 &stored_config,
172 refresh_interval_idx,
173 transcript_poll_interval_idx,
174 config.show_all,
175 config.theme.as_deref(),
176 config.columns,
177 startup_messages,
178 config.expert_mode,
179 );
180
181 let result = infra::run_app(&mut terminal, app);
182
183 disable_raw_mode()?;
185 execute!(
186 terminal.backend_mut(),
187 LeaveAlternateScreen,
188 DisableMouseCapture
189 )?;
190 terminal.show_cursor()?;
191
192 if let Err(e) = result {
193 eprintln!("Error: {e}");
194 }
195
196 Ok(())
197}