Check for missing directories

This commit is contained in:
Ian Keane 2025-12-01 15:09:08 -05:00
parent f3dcd5acbc
commit e62832fde6

View file

@ -85,18 +85,18 @@ impl App {
// Helper to round a time to the nearest 15-minute interval // Helper to round a time to the nearest 15-minute interval
fn round_to_15min(dt: &chrono::DateTime<chrono::Local>) -> chrono::DateTime<chrono::Local> { fn round_to_15min(dt: &chrono::DateTime<chrono::Local>) -> chrono::DateTime<chrono::Local> {
use chrono::{Timelike, Duration}; use chrono::{Timelike, Duration};
let minutes = dt.minute(); let minutes = dt.minute();
let rounded_minutes = ((minutes + 7) / 15) * 15; // Round to nearest 15 let rounded_minutes = ((minutes + 7) / 15) * 15; // Round to nearest 15
let mut rounded = dt.with_minute(0).unwrap().with_second(0).unwrap().with_nanosecond(0).unwrap(); let mut rounded = dt.with_minute(0).unwrap().with_second(0).unwrap().with_nanosecond(0).unwrap();
rounded = rounded + Duration::minutes(rounded_minutes as i64); rounded = rounded + Duration::minutes(rounded_minutes as i64);
// Handle hour overflow (e.g., 50 minutes rounds to 60) // Handle hour overflow (e.g., 50 minutes rounds to 60)
if rounded_minutes >= 60 { if rounded_minutes >= 60 {
rounded = rounded.with_minute(0).unwrap(); rounded = rounded.with_minute(0).unwrap();
} }
rounded rounded
} }
@ -106,29 +106,52 @@ impl App {
if line.trim() == "---" { if line.trim() == "---" {
return false; return false;
} }
match self.log_view_grouping { match self.log_view_grouping {
LogViewGrouping::ByProject => line.starts_with(" "), LogViewGrouping::ByProject => line.starts_with(" "),
LogViewGrouping::ByDate => line.starts_with(" ") && !line.starts_with(" "), LogViewGrouping::ByDate => line.starts_with(" ") && !line.starts_with(" "),
} }
} }
fn ensure_watson_directories() -> anyhow::Result<()> {
// Create watson config directory if it doesn't exist
let watson_dir = dirs::config_dir()
.ok_or_else(|| anyhow::anyhow!("Could not find config directory"))?
.join("watson");
if !watson_dir.exists() {
std::fs::create_dir_all(&watson_dir)?;
}
// Ensure frames file exists
let frames_path = watson_dir.join("frames");
if !frames_path.exists() {
// Create an empty frames file (empty JSON array)
std::fs::write(&frames_path, "[]")?;
}
Ok(())
}
pub fn new() -> anyhow::Result<Self> { pub fn new() -> anyhow::Result<Self> {
// Ensure watson directories and files exist
Self::ensure_watson_directories()?;
let config = Config::load()?; let config = Config::load()?;
let mut state = AppState::load()?; let mut state = AppState::load()?;
// Initialize current_pane to first enabled section // Initialize current_pane to first enabled section
let enabled = [ let enabled = [
config.show_permanent, config.show_permanent,
config.show_recurring, config.show_recurring,
config.show_recent, config.show_recent,
]; ];
// Find first enabled pane // Find first enabled pane
if let Some(first_enabled) = enabled.iter().position(|&x| x) { if let Some(first_enabled) = enabled.iter().position(|&x| x) {
state.current_pane = first_enabled; state.current_pane = first_enabled;
} }
Ok(Self { Ok(Self {
state, state,
config, config,
@ -619,13 +642,13 @@ impl App {
// Group frames by date, tracking their original indices // Group frames by date, tracking their original indices
// Use YYYY-MM-DD as the key for proper sorting // Use YYYY-MM-DD as the key for proper sorting
let mut by_date: BTreeMap<String, (String, Vec<(usize, &WatsonFrame)>)> = BTreeMap::new(); let mut by_date: BTreeMap<String, (String, Vec<(usize, &WatsonFrame)>)> = BTreeMap::new();
// Choose date format based on view period // Choose date format based on view period
let date_format = match self.log_view_period { let date_format = match self.log_view_period {
LogViewPeriod::Month => "%d %B %Y", // "23 November 2025" (no weekday) LogViewPeriod::Month => "%d %B %Y", // "23 November 2025" (no weekday)
_ => "%A %d %B %Y", // "Saturday 23 November 2025" _ => "%A %d %B %Y", // "Saturday 23 November 2025"
}; };
for (idx, frame) in self.log_view_frames.iter().enumerate() { for (idx, frame) in self.log_view_frames.iter().enumerate() {
if let Ok(start_dt) = DateTime::parse_from_rfc3339(&frame.start) { if let Ok(start_dt) = DateTime::parse_from_rfc3339(&frame.start) {
let local_dt: DateTime<Local> = start_dt.into(); let local_dt: DateTime<Local> = start_dt.into();
@ -641,12 +664,12 @@ impl App {
let mut lines = Vec::new(); let mut lines = Vec::new();
let mut frame_indices = Vec::new(); let mut frame_indices = Vec::new();
// Sort each day's frames chronologically // Sort each day's frames chronologically
for (_, frames) in by_date.values_mut() { for (_, frames) in by_date.values_mut() {
frames.sort_by(|(_, a), (_, b)| a.start.cmp(&b.start)); frames.sort_by(|(_, a), (_, b)| a.start.cmp(&b.start));
} }
// Collect dates in the desired order // Collect dates in the desired order
let dates: Vec<_> = match self.log_view_day_order { let dates: Vec<_> = match self.log_view_day_order {
LogViewDayOrder::ReverseChronological => { LogViewDayOrder::ReverseChronological => {
@ -656,12 +679,12 @@ impl App {
by_date.iter().collect() by_date.iter().collect()
} }
}; };
for (_sort_key, (display_date, frames)) in dates { for (_sort_key, (display_date, frames)) in dates {
lines.push(display_date.clone()); lines.push(display_date.clone());
let mut prev_stop: Option<DateTime<Local>> = None; let mut prev_stop: Option<DateTime<Local>> = None;
for (frame_idx, (idx, frame)) in frames.iter().enumerate() { for (frame_idx, (idx, frame)) in frames.iter().enumerate() {
if let (Ok(start_dt), Ok(stop_dt)) = ( if let (Ok(start_dt), Ok(stop_dt)) = (
DateTime::parse_from_rfc3339(&frame.start), DateTime::parse_from_rfc3339(&frame.start),
@ -669,14 +692,14 @@ impl App {
) { ) {
let start_local: DateTime<Local> = start_dt.into(); let start_local: DateTime<Local> = start_dt.into();
let stop_local: DateTime<Local> = stop_dt.into(); let stop_local: DateTime<Local> = stop_dt.into();
// Apply rounding if enabled // Apply rounding if enabled
let (display_start, display_stop) = if self.log_view_rounded { let (display_start, display_stop) = if self.log_view_rounded {
(Self::round_to_15min(&start_local), Self::round_to_15min(&stop_local)) (Self::round_to_15min(&start_local), Self::round_to_15min(&stop_local))
} else { } else {
(start_local, stop_local) (start_local, stop_local)
}; };
// Check for time gap and add separator line if enabled // Check for time gap and add separator line if enabled
if self.config.show_time_gaps && frame_idx > 0 { if self.config.show_time_gaps && frame_idx > 0 {
if let Some(prev) = prev_stop { if let Some(prev) = prev_stop {
@ -687,10 +710,10 @@ impl App {
} }
} }
} }
let start_time = format!("{:02}:{:02}", display_start.hour(), display_start.minute()); let start_time = format!("{:02}:{:02}", display_start.hour(), display_start.minute());
let stop_time = format!("{:02}:{:02}", display_stop.hour(), display_stop.minute()); let stop_time = format!("{:02}:{:02}", display_stop.hour(), display_stop.minute());
// Calculate duration (use rounded times if enabled) // Calculate duration (use rounded times if enabled)
let duration = display_stop.signed_duration_since(display_start); let duration = display_stop.signed_duration_since(display_start);
let hours = duration.num_hours(); let hours = duration.num_hours();
@ -700,21 +723,21 @@ impl App {
} else { } else {
format!("({}m)", minutes) format!("({}m)", minutes)
}; };
let display_text = if frame.tags.is_empty() { let display_text = if frame.tags.is_empty() {
frame.project.clone() frame.project.clone()
} else { } else {
format!("{} [{}]", frame.tags.join(", "), frame.project) format!("{} [{}]", frame.tags.join(", "), frame.project)
}; };
let line_text = format!( let line_text = format!(
" {} to {} {:>9} {}", " {} to {} {:>9} {}",
start_time, stop_time, duration_str, display_text start_time, stop_time, duration_str, display_text
); );
lines.push(line_text); lines.push(line_text);
frame_indices.push(*idx); // Only add actual entries to frame_indices frame_indices.push(*idx); // Only add actual entries to frame_indices
prev_stop = Some(stop_local); prev_stop = Some(stop_local);
} }
} }
@ -732,13 +755,13 @@ impl App {
// Group frames by date, then by project within each date, tracking indices // Group frames by date, then by project within each date, tracking indices
// Use YYYY-MM-DD as the key for proper sorting // Use YYYY-MM-DD as the key for proper sorting
let mut by_date: BTreeMap<String, (String, BTreeMap<String, Vec<(usize, &WatsonFrame)>>)> = BTreeMap::new(); let mut by_date: BTreeMap<String, (String, BTreeMap<String, Vec<(usize, &WatsonFrame)>>)> = BTreeMap::new();
// Choose date format based on view period // Choose date format based on view period
let date_format = match self.log_view_period { let date_format = match self.log_view_period {
LogViewPeriod::Month => "%d %B %Y", // "23 November 2025" (no weekday) LogViewPeriod::Month => "%d %B %Y", // "23 November 2025" (no weekday)
_ => "%A %d %B %Y", // "Saturday 23 November 2025" _ => "%A %d %B %Y", // "Saturday 23 November 2025"
}; };
for (idx, frame) in self.log_view_frames.iter().enumerate() { for (idx, frame) in self.log_view_frames.iter().enumerate() {
if let Ok(start_dt) = DateTime::parse_from_rfc3339(&frame.start) { if let Ok(start_dt) = DateTime::parse_from_rfc3339(&frame.start) {
let local_dt: DateTime<Local> = start_dt.into(); let local_dt: DateTime<Local> = start_dt.into();
@ -756,14 +779,14 @@ impl App {
let mut lines = Vec::new(); let mut lines = Vec::new();
let mut frame_indices = Vec::new(); let mut frame_indices = Vec::new();
// Sort frames within each project chronologically // Sort frames within each project chronologically
for (_, projects) in by_date.values_mut() { for (_, projects) in by_date.values_mut() {
for frames in projects.values_mut() { for frames in projects.values_mut() {
frames.sort_by(|(_, a), (_, b)| a.start.cmp(&b.start)); frames.sort_by(|(_, a), (_, b)| a.start.cmp(&b.start));
} }
} }
// Collect dates in the desired order // Collect dates in the desired order
let dates: Vec<_> = match self.log_view_day_order { let dates: Vec<_> = match self.log_view_day_order {
LogViewDayOrder::ReverseChronological => { LogViewDayOrder::ReverseChronological => {
@ -773,13 +796,13 @@ impl App {
by_date.iter().collect() by_date.iter().collect()
} }
}; };
for (_sort_key, (display_date, projects)) in dates { for (_sort_key, (display_date, projects)) in dates {
lines.push(display_date.clone()); lines.push(display_date.clone());
for (project, frames) in projects.iter() { for (project, frames) in projects.iter() {
lines.push(format!(" {}", project)); // Project header with indent lines.push(format!(" {}", project)); // Project header with indent
for (idx, frame) in frames { for (idx, frame) in frames {
if let (Ok(start_dt), Ok(stop_dt)) = ( if let (Ok(start_dt), Ok(stop_dt)) = (
DateTime::parse_from_rfc3339(&frame.start), DateTime::parse_from_rfc3339(&frame.start),
@ -787,17 +810,17 @@ impl App {
) { ) {
let start_local: DateTime<Local> = start_dt.into(); let start_local: DateTime<Local> = start_dt.into();
let stop_local: DateTime<Local> = stop_dt.into(); let stop_local: DateTime<Local> = stop_dt.into();
// Apply rounding if enabled // Apply rounding if enabled
let (display_start, display_stop) = if self.log_view_rounded { let (display_start, display_stop) = if self.log_view_rounded {
(Self::round_to_15min(&start_local), Self::round_to_15min(&stop_local)) (Self::round_to_15min(&start_local), Self::round_to_15min(&stop_local))
} else { } else {
(start_local, stop_local) (start_local, stop_local)
}; };
let start_time = format!("{:02}:{:02}", display_start.hour(), display_start.minute()); let start_time = format!("{:02}:{:02}", display_start.hour(), display_start.minute());
let stop_time = format!("{:02}:{:02}", display_stop.hour(), display_stop.minute()); let stop_time = format!("{:02}:{:02}", display_stop.hour(), display_stop.minute());
// Calculate duration (use rounded times if enabled) // Calculate duration (use rounded times if enabled)
let duration = display_stop.signed_duration_since(display_start); let duration = display_stop.signed_duration_since(display_start);
let hours = duration.num_hours(); let hours = duration.num_hours();
@ -807,13 +830,13 @@ impl App {
} else { } else {
format!("({}m)", minutes) format!("({}m)", minutes)
}; };
let tags_str = if frame.tags.is_empty() { let tags_str = if frame.tags.is_empty() {
String::new() String::new()
} else { } else {
format!(" [{}]", frame.tags.join(", ")) format!(" [{}]", frame.tags.join(", "))
}; };
lines.push(format!( lines.push(format!(
" {} to {} {:>9}{}", " {} to {} {:>9}{}",
start_time, stop_time, duration_str, tags_str start_time, stop_time, duration_str, tags_str
@ -834,7 +857,7 @@ impl App {
if self.log_view_selected >= self.log_view_frame_indices.len() { if self.log_view_selected >= self.log_view_frame_indices.len() {
return Ok(()); return Ok(());
} }
// Get the actual frame index from the display index // Get the actual frame index from the display index
let frame_idx = self.log_view_frame_indices[self.log_view_selected]; let frame_idx = self.log_view_frame_indices[self.log_view_selected];
if frame_idx >= self.log_view_frames.len() { if frame_idx >= self.log_view_frames.len() {
@ -876,7 +899,7 @@ impl App {
if self.log_view_selected >= self.log_view_frame_indices.len() { if self.log_view_selected >= self.log_view_frame_indices.len() {
return Ok(()); return Ok(());
} }
// Get the actual frame index from the display index // Get the actual frame index from the display index
let frame_idx = self.log_view_frame_indices[self.log_view_selected]; let frame_idx = self.log_view_frame_indices[self.log_view_selected];
if frame_idx >= self.log_view_frames.len() { if frame_idx >= self.log_view_frames.len() {
@ -895,7 +918,7 @@ impl App {
if output.status.success() { if output.status.success() {
// Reload log content // Reload log content
self.load_log_content()?; self.load_log_content()?;
// Adjust selection if we deleted the last item // Adjust selection if we deleted the last item
if self.log_view_selected >= self.log_view_frame_indices.len() && !self.log_view_frame_indices.is_empty() { if self.log_view_selected >= self.log_view_frame_indices.len() && !self.log_view_frame_indices.is_empty() {
self.log_view_selected = self.log_view_frame_indices.len() - 1; self.log_view_selected = self.log_view_frame_indices.len() - 1;
@ -910,25 +933,25 @@ impl App {
fn fix_entry_gap(&mut self) -> anyhow::Result<()> { fn fix_entry_gap(&mut self) -> anyhow::Result<()> {
use chrono::DateTime; use chrono::DateTime;
use serde_json::Value; use serde_json::Value;
self.set_status_message("Backfill function called"); self.set_status_message("Backfill function called");
// Check if selection is valid // Check if selection is valid
if self.log_view_selected >= self.log_view_frame_indices.len() { if self.log_view_selected >= self.log_view_frame_indices.len() {
self.set_status_message("Invalid selection"); self.set_status_message("Invalid selection");
return Ok(()); return Ok(());
} }
// Can't fix the first entry // Can't fix the first entry
if self.log_view_selected == 0 { if self.log_view_selected == 0 {
self.set_status_message("No previous entry!"); self.set_status_message("No previous entry!");
return Ok(()); return Ok(());
} }
// Check if previous entry is on the same day // Check if previous entry is on the same day
let current_frame_idx = self.log_view_frame_indices[self.log_view_selected]; let current_frame_idx = self.log_view_frame_indices[self.log_view_selected];
let prev_frame_idx = self.log_view_frame_indices[self.log_view_selected - 1]; let prev_frame_idx = self.log_view_frame_indices[self.log_view_selected - 1];
if let (Some(current), Some(prev)) = ( if let (Some(current), Some(prev)) = (
self.log_view_frames.get(current_frame_idx), self.log_view_frames.get(current_frame_idx),
self.log_view_frames.get(prev_frame_idx), self.log_view_frames.get(prev_frame_idx),
@ -941,21 +964,21 @@ impl App {
// Check if they're on the same day // Check if they're on the same day
let curr_date = curr_start.format("%Y-%m-%d").to_string(); let curr_date = curr_start.format("%Y-%m-%d").to_string();
let prev_date = prev_stop.format("%Y-%m-%d").to_string(); let prev_date = prev_stop.format("%Y-%m-%d").to_string();
if curr_date != prev_date { if curr_date != prev_date {
self.set_status_message("No previous entry on same day!"); self.set_status_message("No previous entry on same day!");
return Ok(()); return Ok(());
} }
// Read watson frames file // Read watson frames file
let frames_path = dirs::config_dir() let frames_path = dirs::config_dir()
.ok_or_else(|| anyhow::anyhow!("Could not find config directory"))? .ok_or_else(|| anyhow::anyhow!("Could not find config directory"))?
.join("watson") .join("watson")
.join("frames"); .join("frames");
let frames_content = std::fs::read_to_string(&frames_path)?; let frames_content = std::fs::read_to_string(&frames_path)?;
let mut frames: Value = serde_json::from_str(&frames_content)?; let mut frames: Value = serde_json::from_str(&frames_content)?;
// Find and update the frame with matching id // Find and update the frame with matching id
if let Some(frames_array) = frames.as_array_mut() { if let Some(frames_array) = frames.as_array_mut() {
for frame in frames_array { for frame in frames_array {
@ -974,17 +997,17 @@ impl App {
} }
} }
} }
// Write back to frames file // Write back to frames file
let updated_content = serde_json::to_string_pretty(&frames)?; let updated_content = serde_json::to_string_pretty(&frames)?;
std::fs::write(&frames_path, updated_content)?; std::fs::write(&frames_path, updated_content)?;
// Reload log content // Reload log content
self.load_log_content()?; self.load_log_content()?;
self.set_status_message("Entry start time adjusted"); self.set_status_message("Entry start time adjusted");
} }
} }
self.needs_clear = true; self.needs_clear = true;
Ok(()) Ok(())
} }
@ -1044,7 +1067,7 @@ impl App {
.iter() .iter()
.filter(|l| self.is_entry_line(l)) .filter(|l| self.is_entry_line(l))
.collect(); .collect();
if self.log_view_selected < entry_lines.len() { if self.log_view_selected < entry_lines.len() {
entry_lines[self.log_view_selected].to_string() entry_lines[self.log_view_selected].to_string()
} else { } else {
@ -1096,7 +1119,7 @@ impl App {
} else if line.starts_with('\t') || line.starts_with(" ") { } else if line.starts_with('\t') || line.starts_with(" ") {
// Add all lines within the day (project headers and entries) // Add all lines within the day (project headers and entries)
current_day_lines.push(line.clone()); current_day_lines.push(line.clone());
// Count only actual entries // Count only actual entries
if self.is_entry_line(line) { if self.is_entry_line(line) {
if frame_count == self.log_view_selected { if frame_count == self.log_view_selected {
@ -1118,7 +1141,7 @@ impl App {
let mut selected_date = String::new(); let mut selected_date = String::new();
let mut frame_count = 0; let mut frame_count = 0;
let mut found_current = false; let mut found_current = false;
// Find which project we're currently in // Find which project we're currently in
for line in &self.log_view_content.clone() { for line in &self.log_view_content.clone() {
if !line.starts_with('\t') && !line.is_empty() && !line.starts_with(" ") { if !line.starts_with('\t') && !line.is_empty() && !line.starts_with(" ") {
@ -1126,7 +1149,7 @@ impl App {
} else if line.starts_with(" ") && !line.starts_with("\t") { } else if line.starts_with(" ") && !line.starts_with("\t") {
current_project = line.clone(); current_project = line.clone();
} }
if self.is_entry_line(line) { if self.is_entry_line(line) {
if frame_count == self.log_view_selected { if frame_count == self.log_view_selected {
selected_project = current_project.clone(); selected_project = current_project.clone();
@ -1137,17 +1160,17 @@ impl App {
frame_count += 1; frame_count += 1;
} }
} }
if !found_current { if !found_current {
return; return;
} }
// Now find the next project within the same day first, then next day // Now find the next project within the same day first, then next day
current_project = String::new(); current_project = String::new();
current_date = String::new(); current_date = String::new();
let mut past_selected = false; let mut past_selected = false;
frame_count = 0; frame_count = 0;
for line in &self.log_view_content.clone() { for line in &self.log_view_content.clone() {
if !line.starts_with('\t') && !line.is_empty() && !line.starts_with(" ") { if !line.starts_with('\t') && !line.is_empty() && !line.starts_with(" ") {
current_date = line.clone(); current_date = line.clone();
@ -1159,7 +1182,7 @@ impl App {
return; return;
} }
} }
if self.is_entry_line(line) { if self.is_entry_line(line) {
if current_project == selected_project && current_date == selected_date { if current_project == selected_project && current_date == selected_date {
past_selected = true; past_selected = true;
@ -1176,7 +1199,7 @@ impl App {
let mut frame_count = 0; let mut frame_count = 0;
let mut previous_project_first_entry = None; let mut previous_project_first_entry = None;
let mut last_different_project_entry = None; let mut last_different_project_entry = None;
// Find which project we're currently in // Find which project we're currently in
for line in &self.log_view_content.clone() { for line in &self.log_view_content.clone() {
if line.starts_with(" ") && !line.starts_with("\t") { if line.starts_with(" ") && !line.starts_with("\t") {
@ -1186,7 +1209,7 @@ impl App {
current_project = line.clone(); current_project = line.clone();
previous_project_first_entry = Some(frame_count); previous_project_first_entry = Some(frame_count);
} }
if self.is_entry_line(line) { if self.is_entry_line(line) {
if frame_count == self.log_view_selected { if frame_count == self.log_view_selected {
_selected_project = current_project.clone(); _selected_project = current_project.clone();
@ -1207,13 +1230,13 @@ impl App {
let mut selected_date = String::new(); let mut selected_date = String::new();
let mut frame_count = 0; let mut frame_count = 0;
let mut found_current = false; let mut found_current = false;
// Find which day we're currently in // Find which day we're currently in
for line in &self.log_view_content.clone() { for line in &self.log_view_content.clone() {
if !line.starts_with('\t') && !line.is_empty() && !line.starts_with(" ") { if !line.starts_with('\t') && !line.is_empty() && !line.starts_with(" ") {
current_date = line.clone(); current_date = line.clone();
} }
if self.is_entry_line(line) { if self.is_entry_line(line) {
if frame_count == self.log_view_selected { if frame_count == self.log_view_selected {
selected_date = current_date.clone(); selected_date = current_date.clone();
@ -1223,16 +1246,16 @@ impl App {
frame_count += 1; frame_count += 1;
} }
} }
if !found_current { if !found_current {
return; return;
} }
// Now find the next day and jump to its first entry // Now find the next day and jump to its first entry
current_date = String::new(); current_date = String::new();
let mut past_selected = false; let mut past_selected = false;
frame_count = 0; frame_count = 0;
for line in &self.log_view_content.clone() { for line in &self.log_view_content.clone() {
if !line.starts_with('\t') && !line.is_empty() && !line.starts_with(" ") { if !line.starts_with('\t') && !line.is_empty() && !line.starts_with(" ") {
if past_selected && line != &selected_date { if past_selected && line != &selected_date {
@ -1242,7 +1265,7 @@ impl App {
past_selected = true; past_selected = true;
} }
} }
if self.is_entry_line(line) { if self.is_entry_line(line) {
if past_selected && !current_date.is_empty() && current_date != selected_date { if past_selected && !current_date.is_empty() && current_date != selected_date {
// First entry of next day // First entry of next day
@ -1261,7 +1284,7 @@ impl App {
let mut frame_count = 0; let mut frame_count = 0;
let mut last_different_day_entry = None; let mut last_different_day_entry = None;
let mut previous_day_first_entry = None; let mut previous_day_first_entry = None;
// Find which day we're currently in // Find which day we're currently in
for line in &self.log_view_content.clone() { for line in &self.log_view_content.clone() {
if !line.starts_with('\t') && !line.is_empty() && !line.starts_with(" ") { if !line.starts_with('\t') && !line.is_empty() && !line.starts_with(" ") {
@ -1271,7 +1294,7 @@ impl App {
current_date = line.clone(); current_date = line.clone();
previous_day_first_entry = Some(frame_count); previous_day_first_entry = Some(frame_count);
} }
if self.is_entry_line(line) { if self.is_entry_line(line) {
if frame_count == self.log_view_selected { if frame_count == self.log_view_selected {
_selected_date = current_date.clone(); _selected_date = current_date.clone();
@ -1300,10 +1323,10 @@ impl App {
let current = self.state.selected_indices[self.state.current_pane] as i32; let current = self.state.selected_indices[self.state.current_pane] as i32;
let items_len = items.len() as i32; let items_len = items.len() as i32;
// Check if we're using two-column mode // Check if we're using two-column mode
let use_two_columns = self.config.multi_column && items.len() > 6; let use_two_columns = self.config.multi_column && items.len() > 6;
if use_two_columns { if use_two_columns {
let mid_point = (items.len() + 1) / 2; let mid_point = (items.len() + 1) / 2;
let (col_start, col_end) = if self.state.current_column == 0 { let (col_start, col_end) = if self.state.current_column == 0 {
@ -1311,9 +1334,9 @@ impl App {
} else { } else {
(mid_point as i32, items_len - 1) (mid_point as i32, items_len - 1)
}; };
let new_index = current + delta; let new_index = current + delta;
if new_index < col_start && delta < 0 { if new_index < col_start && delta < 0 {
// Moving up beyond column - go to previous pane // Moving up beyond column - go to previous pane
self.change_pane(-1); self.change_pane(-1);
@ -1339,13 +1362,13 @@ impl App {
self.state.current_column = 0; self.state.current_column = 0;
} else { } else {
// Normal movement within column // Normal movement within column
self.state.selected_indices[self.state.current_pane] = self.state.selected_indices[self.state.current_pane] =
new_index.clamp(col_start, col_end) as usize; new_index.clamp(col_start, col_end) as usize;
} }
} else { } else {
// Single column mode // Single column mode
let new_index = current + delta; let new_index = current + delta;
if new_index < 0 && delta < 0 { if new_index < 0 && delta < 0 {
// At top, move to previous pane // At top, move to previous pane
self.change_pane(-1); self.change_pane(-1);
@ -1364,45 +1387,45 @@ impl App {
self.state.selected_indices[self.state.current_pane] = 0; self.state.selected_indices[self.state.current_pane] = 0;
} else { } else {
// Normal movement within pane // Normal movement within pane
self.state.selected_indices[self.state.current_pane] = self.state.selected_indices[self.state.current_pane] =
new_index.clamp(0, items_len - 1) as usize; new_index.clamp(0, items_len - 1) as usize;
} }
} }
} }
fn move_column(&mut self, delta: i32) { fn move_column(&mut self, delta: i32) {
if !self.config.multi_column { if !self.config.multi_column {
return; return;
} }
let items = match self.state.current_pane { let items = match self.state.current_pane {
0 => &self.state.permanent_items, 0 => &self.state.permanent_items,
1 => &self.state.recurring_items, 1 => &self.state.recurring_items,
2 => &self.state.recent_items, 2 => &self.state.recent_items,
_ => return, _ => return,
}; };
// Only switch columns if we have enough items for two columns // Only switch columns if we have enough items for two columns
if items.len() <= 6 { if items.len() <= 6 {
return; return;
} }
let mid_point = (items.len() + 1) / 2; let mid_point = (items.len() + 1) / 2;
let new_column = if delta > 0 { 1 } else { 0 }; let new_column = if delta > 0 { 1 } else { 0 };
if new_column != self.state.current_column { if new_column != self.state.current_column {
let current_selected = self.state.selected_indices[self.state.current_pane]; let current_selected = self.state.selected_indices[self.state.current_pane];
// Calculate which row we're on in the current column // Calculate which row we're on in the current column
let current_row = if self.state.current_column == 0 { let current_row = if self.state.current_column == 0 {
current_selected // In left column current_selected // In left column
} else { } else {
current_selected.saturating_sub(mid_point) // In right column current_selected.saturating_sub(mid_point) // In right column
}; };
// Switch column and jump to the same row in the new column // Switch column and jump to the same row in the new column
self.state.current_column = new_column; self.state.current_column = new_column;
if new_column == 0 { if new_column == 0 {
// Moving to left column - jump to same row // Moving to left column - jump to same row
self.state.selected_indices[self.state.current_pane] = current_row.min(mid_point - 1); self.state.selected_indices[self.state.current_pane] = current_row.min(mid_point - 1);
@ -1413,7 +1436,7 @@ impl App {
} }
} }
} }
fn change_pane(&mut self, delta: i32) { fn change_pane(&mut self, delta: i32) {
// Find next enabled pane // Find next enabled pane
let mut next_pane = self.state.current_pane; let mut next_pane = self.state.current_pane;
@ -1422,13 +1445,13 @@ impl App {
self.config.show_recurring, self.config.show_recurring,
self.config.show_recent, self.config.show_recent,
]; ];
// Count enabled panes // Count enabled panes
let enabled_count = enabled.iter().filter(|&&x| x).count(); let enabled_count = enabled.iter().filter(|&&x| x).count();
if enabled_count == 0 { if enabled_count == 0 {
return; // No panes to switch to return; // No panes to switch to
} }
// Find next enabled pane // Find next enabled pane
for _ in 0..3 { for _ in 0..3 {
next_pane = ((next_pane as i32 + delta).rem_euclid(3)) as usize; next_pane = ((next_pane as i32 + delta).rem_euclid(3)) as usize;
@ -1436,7 +1459,7 @@ impl App {
break; break;
} }
} }
self.state.current_pane = next_pane; self.state.current_pane = next_pane;
self.state.current_column = 0; self.state.current_column = 0;
} }
@ -1578,7 +1601,7 @@ impl App {
if output.status.success() { if output.status.success() {
let json_str = String::from_utf8_lossy(&output.stdout); let json_str = String::from_utf8_lossy(&output.stdout);
// Parse JSON frames // Parse JSON frames
match serde_json::from_str::<Vec<WatsonFrame>>(&json_str) { match serde_json::from_str::<Vec<WatsonFrame>>(&json_str) {
Ok(frames) => { Ok(frames) => {
@ -1668,7 +1691,7 @@ impl App {
// Return to TUI mode // Return to TUI mode
execute!(stdout(), EnterAlternateScreen)?; execute!(stdout(), EnterAlternateScreen)?;
enable_raw_mode()?; enable_raw_mode()?;
self.needs_clear = true; self.needs_clear = true;
Ok(()) Ok(())
@ -1705,7 +1728,7 @@ impl App {
// Return to TUI mode // Return to TUI mode
execute!(stdout(), EnterAlternateScreen)?; execute!(stdout(), EnterAlternateScreen)?;
enable_raw_mode()?; enable_raw_mode()?;
self.needs_clear = true; self.needs_clear = true;
Ok(()) Ok(())