use crate::effects::EffectsManager;
use crate::menu::Menu;
use crate::stream::StreamState;
use std::time::Instant;
#[derive(Debug)]
pub struct Model {
pub state: AppState,
pub menu: Menu,
pub effects: EffectsManager,
pub last_tick: Instant,
pub command_result: Option<String>,
pub command_error: Option<String>,
pub loading_counter: u64,
pub scroll_position: usize,
pub result_lines: Vec<String>,
pub wrapped_lines: Vec<String>,
pub reveal_counter: u64,
pub running: bool,
pub stream_state: Option<StreamState>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AppState {
Startup,
Menu,
Executing,
Loading,
ShowResult,
}
impl Default for Model {
fn default() -> Self {
Self::new()
}
}
impl Model {
pub fn new() -> Self {
Self {
state: AppState::Startup,
menu: Menu::new(),
effects: EffectsManager::new(),
last_tick: Instant::now(),
command_result: None,
command_error: None,
loading_counter: 0,
scroll_position: 0,
result_lines: Vec::new(),
wrapped_lines: Vec::new(),
reveal_counter: 0,
running: true,
stream_state: None,
}
}
pub fn should_quit(&self) -> bool {
!self.running
}
pub fn is_showing_result(&self) -> bool {
self.state == AppState::ShowResult
}
pub fn is_in_menu(&self) -> bool {
self.state == AppState::Menu
}
pub fn is_startup_complete(&self) -> bool {
self.state != AppState::Startup || self.effects.is_startup_complete()
}
pub fn get_selected_command(&self) -> Option<String> {
self.menu.get_selected_command()
}
pub fn clear_results(&mut self) {
self.command_result = None;
self.command_error = None;
self.scroll_position = 0;
self.result_lines.clear();
self.wrapped_lines.clear();
}
pub fn set_result(&mut self, output: String) {
self.command_result = Some(output.clone());
self.result_lines = output.lines().map(|s| s.to_string()).collect();
self.scroll_position = 0;
self.reveal_counter = 0;
}
pub fn set_error(&mut self, error: String) {
self.command_error = Some(error.clone());
self.result_lines = error.lines().map(|s| s.to_string()).collect();
self.scroll_position = 0;
self.reveal_counter = 0;
}
pub fn total_result_lines(&self) -> usize {
self.wrapped_lines.len()
}
pub fn can_scroll(&self) -> bool {
self.wrapped_lines.len() > 1
}
pub fn update_wrapped_lines(&mut self, max_width: usize) {
if self.result_lines.is_empty() {
self.wrapped_lines = vec!["No output".to_string()];
return;
}
self.wrapped_lines = self
.result_lines
.iter()
.flat_map(|line| wrap_line(line, max_width))
.collect();
}
}
fn wrap_line(line: &str, max_width: usize) -> Vec<String> {
if line.len() <= max_width {
return vec![line.to_string()];
}
let mut chunks = Vec::new();
let mut current_chunk = String::new();
let mut current_length = 0;
for word in line.split_whitespace() {
if current_length + word.len() < max_width {
if !current_chunk.is_empty() {
current_chunk.push(' ');
current_length += 1;
}
current_chunk.push_str(word);
current_length += word.len();
} else {
if !current_chunk.is_empty() {
chunks.push(current_chunk);
}
if word.len() > max_width {
for chunk in word.chars().collect::<Vec<char>>().chunks(max_width) {
chunks.push(chunk.iter().collect::<String>());
}
current_chunk = String::new();
current_length = 0;
} else {
current_chunk = word.to_string();
current_length = word.len();
}
}
}
if !current_chunk.is_empty() {
chunks.push(current_chunk);
}
if chunks.is_empty() {
vec![line.to_string()]
} else {
chunks
}
}