1pub mod handler;
8pub mod screens;
9pub mod state;
10pub mod ui;
11
12use crate::errors::Result;
13use crossterm::{
14 event::{DisableMouseCapture, EnableMouseCapture, Event as CtEvent},
15 execute,
16 terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
17};
18use ratatui::backend::CrosstermBackend;
19use ratatui::Terminal;
20use state::{SetupOutcome, SetupState, SetupStep};
21use std::io;
22use std::time::Duration;
23
24pub async fn run_setup() -> Result<bool> {
29 let default_path = std::env::current_dir()
30 .map(|p| state::tilde_collapse(&p.to_string_lossy()))
31 .unwrap_or_else(|_| "~/Git-Same/GitHub".to_string());
32 let mut state = SetupState::new(&default_path);
33
34 struct SetupTerminalGuard {
35 raw_enabled: bool,
36 alt_enabled: bool,
37 }
38 impl Drop for SetupTerminalGuard {
39 fn drop(&mut self) {
40 if self.alt_enabled {
41 let mut stdout = io::stdout();
42 let _ = execute!(stdout, LeaveAlternateScreen, DisableMouseCapture);
43 }
44 if self.raw_enabled {
45 let _ = disable_raw_mode();
46 }
47 }
48 }
49
50 enable_raw_mode()?;
52 let mut guard = SetupTerminalGuard {
53 raw_enabled: true,
54 alt_enabled: false,
55 };
56 let mut stdout = io::stdout();
57 execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?;
58 guard.alt_enabled = true;
59 let backend = CrosstermBackend::new(stdout);
60 let mut terminal = Terminal::new(backend)?;
61
62 let result = run_wizard(&mut terminal, &mut state).await;
64
65 let _ = disable_raw_mode();
67 guard.raw_enabled = false;
68 let _ = execute!(
69 terminal.backend_mut(),
70 LeaveAlternateScreen,
71 DisableMouseCapture
72 );
73 guard.alt_enabled = false;
74 let _ = terminal.show_cursor();
75
76 result?;
77
78 Ok(matches!(state.outcome, Some(SetupOutcome::Completed)))
79}
80
81async fn run_wizard(
82 terminal: &mut Terminal<CrosstermBackend<io::Stdout>>,
83 state: &mut SetupState,
84) -> Result<()> {
85 loop {
86 terminal.draw(|frame| ui::render(state, frame))?;
87
88 if maybe_start_requirements_checks(state) {
91 terminal.draw(|frame| ui::render(state, frame))?;
92 run_requirements_checks(state).await;
93 continue;
94 }
95
96 if state.step == SetupStep::SelectOrgs && state.org_loading {
98 terminal.draw(|frame| ui::render(state, frame))?;
100 handler::handle_key(
101 state,
102 crossterm::event::KeyEvent::new(
103 crossterm::event::KeyCode::Null,
104 crossterm::event::KeyModifiers::NONE,
105 ),
106 )
107 .await;
108 continue;
109 }
110
111 state.tick_count = state.tick_count.wrapping_add(1);
113
114 if crossterm::event::poll(Duration::from_millis(100))? {
116 if let Ok(event) = crossterm::event::read() {
117 match event {
118 CtEvent::Key(key) => {
119 handler::handle_key(state, key).await;
120 }
121 CtEvent::Resize(_, _) => {
122 }
124 _ => {}
125 }
126 }
127 }
128
129 if state.should_quit {
130 break;
131 }
132 }
133 Ok(())
134}
135
136pub(crate) fn maybe_start_requirements_checks(state: &mut SetupState) -> bool {
137 if state.step != SetupStep::Requirements || state.checks_triggered {
138 return false;
139 }
140
141 state.checks_triggered = true;
142 state.checks_loading = true;
143 state.config_path_display = crate::config::Config::default_path()
144 .ok()
145 .map(|p| p.display().to_string());
146 true
147}
148
149pub(crate) fn apply_requirements_check_results(
150 state: &mut SetupState,
151 results: Vec<crate::checks::CheckResult>,
152) {
153 state.check_results = results;
154 state.checks_loading = false;
155}
156
157pub(crate) async fn run_requirements_checks(state: &mut SetupState) {
158 let results = crate::checks::check_requirements().await;
159 apply_requirements_check_results(state, results);
160}
161
162#[cfg(test)]
163#[path = "mod_tests.rs"]
164mod tests;