From 79f3a2e13bc59677480042164c16e2c2b2591088 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=81nis?= Date: Sat, 16 May 2026 22:01:02 +0300 Subject: [PATCH] Remember last selection when navigating back MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pushed the selected index onto a stack on every drill-down (library → series → season → episode) and pop it on back so the cursor lands on the row the user just opened, instead of snapping to row 0. Clamps the restored index to the current list length to survive a shrunk list. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/app.rs | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/src/app.rs b/src/app.rs index ec08c8d..727cbab 100644 --- a/src/app.rs +++ b/src/app.rs @@ -35,6 +35,10 @@ pub struct App { pub list_state: ListState, pub current_title: Option, pub breadcrumbs: Vec<(String, String)>, // (id, name) + /// Selected index at each level we navigated away from, so `back` can restore + /// the cursor where the user left it (e.g. Series → Season → Episode → back + /// brings you to the same season/series row, not row 0). + pub selection_stack: Vec, pub status: String, } @@ -66,6 +70,7 @@ impl App { list_state, current_title: None, breadcrumbs: vec![], + selection_stack: vec![], status, }) } @@ -122,6 +127,9 @@ impl App { let Some(entry) = self.home_entries.get(i).cloned() else { return Ok(None); }; + // Remember which library/virtual entry was selected for restore on back. + self.selection_stack.clear(); + self.selection_stack.push(i); if entry.id == RESUME_VIRTUAL_ID { self.items = self.resume.clone(); self.view = View::Items; @@ -151,6 +159,8 @@ impl App { return Ok(None); }; if item.is_folder { + // Remember position in this folder before drilling down. + self.selection_stack.push(i); self.status = format!("Loading {}...", item.name); self.items = self.client.items(&item.id).await?; self.breadcrumbs.push((item.id.clone(), item.name.clone())); @@ -182,6 +192,7 @@ impl App { } // Pop the current breadcrumb (current folder) self.breadcrumbs.pop(); + let restore = self.selection_stack.pop(); if let Some((parent_id, parent_name)) = self.breadcrumbs.last().cloned() { if parent_id == RESUME_VIRTUAL_ID { self.items = self.resume.clone(); @@ -189,18 +200,14 @@ impl App { self.items = self.client.items(&parent_id).await?; } self.current_title = Some(parent_name.clone()); - self.list_state - .select(if self.items.is_empty() { None } else { Some(0) }); + self.list_state.select(restore_selection(restore, self.items.len())); self.status = format!("{}: {} items", parent_name, self.items.len()); } else { self.view = View::Libraries; self.current_title = None; self.items.clear(); - self.list_state.select(if self.home_entries.is_empty() { - None - } else { - Some(0) - }); + self.list_state + .select(restore_selection(restore, self.home_entries.len())); self.status = format!( "{} libraries · {} in progress", self.libraries.len(), @@ -245,6 +252,15 @@ impl App { } } +/// Clamp the remembered selection to the current list size. Returns None if +/// the list is empty (so no row is highlighted). +fn restore_selection(saved: Option, len: usize) -> Option { + if len == 0 { + return None; + } + Some(saved.unwrap_or(0).min(len - 1)) +} + fn build_home_entries(libraries: &[Item], resume: &[Item]) -> Vec { let mut entries: Vec = Vec::with_capacity(libraries.len() + 1); if !resume.is_empty() {