diff options
| -rw-r--r-- | TODO.md | 36 | ||||
| -rw-r--r-- | src/queue.rs | 22 | ||||
| -rw-r--r-- | tests/queue.rs | 50 |
3 files changed, 65 insertions, 43 deletions
| @@ -62,35 +62,13 @@ | |||
| 62 | 62 | ||
| 63 | ## Remaining Work | 63 | ## Remaining Work |
| 64 | 64 | ||
| 65 | ### Phase 1: Play Command Rewrite | 65 | ### Phase 1-3: Completed ✓ |
| 66 | 66 | ||
| 67 | - [ ] **Rewrite `rsp play` behavior** | 67 | - [x] **Play command rewrite** - No args queues to end (play if queue empty + not playing), -i inserts and plays, -n inserts without playing, removed -l flag |
| 68 | - No options: Add to end of queue. If queue is empty AND player is in PLAY state, play immediately | 68 | - [x] **Queue display rewrite** - Returns full output (client can pipe to pager), removed daemon-side pager (fails without TTY) |
| 69 | - `-i` / `--now`: Add after current track, then skip to it (insert + play) | 69 | - [x] **Shuffle rewrite** - Always remembers original queue, shuffle none keeps played tracks in place, album shuffle groups by album |
| 70 | - `-n` / `--next`: Add after current track, don't skip | 70 | |
| 71 | - Remove `-l` / `--last` flag (redundant, it's default behavior) | 71 | ### Optional Features |
| 72 | - Update CLI definitions in `src/cli.rs` | ||
| 73 | - Update daemon handler in `src/daemon.rs` | ||
| 74 | |||
| 75 | ### Phase 2: Queue Display Rewrite | ||
| 76 | |||
| 77 | - [ ] **Rewrite `rsp queue` behavior** | ||
| 78 | - If queue rows <= terminal rows, display as-is (no pager needed) | ||
| 79 | - If queue rows > terminal rows, pipe to pager (e.g., `less -R`) | ||
| 80 | - Get terminal size using `terminal_size` crate or `ansi_term` / `crossterm` | ||
| 81 | - Update queue display logic in `src/daemon.rs` | ||
| 82 | |||
| 83 | ### Phase 3: Shuffle Rewrite | ||
| 84 | |||
| 85 | - [ ] **Rewrite shuffle mode** | ||
| 86 | - Always remember original queue (add `original_tracks: Vec<Track>` field) | ||
| 87 | - `rsp shuffle all`: Shuffle tracks after current position once, store original queue | ||
| 88 | - `rsp shuffle none`: Restore from original queue - keep already-played tracks in place, insert unplayed tracks in original order after current position | ||
| 89 | - `rsp shuffle album`: Shuffle tracks within each album's range separately (don't mix albums) | ||
| 90 | - Update `Queue` struct in `src/queue.rs` with original queue storage | ||
| 91 | - Update shuffle logic in `src/queue.rs` and daemon handlers in `src/daemon.rs` | ||
| 92 | |||
| 93 | ### Phase 4: Optional Features | ||
| 94 | 72 | ||
| 95 | - [ ] **Persist queue state** - Save queue to disk so it survives daemon restarts. | 73 | - [ ] **Persist queue state** - Save queue to disk so it survives daemon restarts. |
| 96 | 74 | ||
diff --git a/src/queue.rs b/src/queue.rs index f556ed3..edd973f 100644 --- a/src/queue.rs +++ b/src/queue.rs | |||
| @@ -191,8 +191,12 @@ impl Queue { | |||
| 191 | 191 | ||
| 192 | let current_pos = self.position; | 192 | let current_pos = self.position; |
| 193 | let indices_len = self.shuffled_indices.len().min(10); | 193 | let indices_len = self.shuffled_indices.len().min(10); |
| 194 | eprintln!("DEBUG restore: current_pos={}, tracks.len={}, shuffled_indices={:?}", | 194 | eprintln!( |
| 195 | current_pos, self.tracks.len(), &self.shuffled_indices[..indices_len]); | 195 | "DEBUG restore: current_pos={}, tracks.len={}, shuffled_indices={:?}", |
| 196 | current_pos, | ||
| 197 | self.tracks.len(), | ||
| 198 | &self.shuffled_indices[..indices_len] | ||
| 199 | ); | ||
| 196 | 200 | ||
| 197 | // Find which tracks have been played (positions 0..=current_pos in shuffled order) | 201 | // Find which tracks have been played (positions 0..=current_pos in shuffled order) |
| 198 | let mut played_track_paths: Vec<std::path::PathBuf> = Vec::new(); | 202 | let mut played_track_paths: Vec<std::path::PathBuf> = Vec::new(); |
| @@ -200,8 +204,12 @@ impl Queue { | |||
| 200 | if let Some(&idx) = self.shuffled_indices.get(i) | 204 | if let Some(&idx) = self.shuffled_indices.get(i) |
| 201 | && let Some(track) = self.tracks.get(idx) | 205 | && let Some(track) = self.tracks.get(idx) |
| 202 | { | 206 | { |
| 203 | eprintln!("DEBUG: played track at shuffled pos {} -> track idx {} = {:?}", | 207 | eprintln!( |
| 204 | i, idx, track.path.file_name()); | 208 | "DEBUG: played track at shuffled pos {} -> track idx {} = {:?}", |
| 209 | i, | ||
| 210 | idx, | ||
| 211 | track.path.file_name() | ||
| 212 | ); | ||
| 205 | played_track_paths.push(track.path.clone()); | 213 | played_track_paths.push(track.path.clone()); |
| 206 | } | 214 | } |
| 207 | } | 215 | } |
| @@ -220,7 +228,11 @@ impl Queue { | |||
| 220 | for (i, orig_track) in self.original_tracks.iter().enumerate() { | 228 | for (i, orig_track) in self.original_tracks.iter().enumerate() { |
| 221 | let is_played = played_track_paths.iter().any(|p| p == &orig_track.path); | 229 | let is_played = played_track_paths.iter().any(|p| p == &orig_track.path); |
| 222 | if !is_played { | 230 | if !is_played { |
| 223 | eprintln!("DEBUG: adding unplayed from original idx {}: {:?}", i, orig_track.path.file_name()); | 231 | eprintln!( |
| 232 | "DEBUG: adding unplayed from original idx {}: {:?}", | ||
| 233 | i, | ||
| 234 | orig_track.path.file_name() | ||
| 235 | ); | ||
| 224 | new_tracks.push(orig_track.clone()); | 236 | new_tracks.push(orig_track.clone()); |
| 225 | } | 237 | } |
| 226 | } | 238 | } |
diff --git a/tests/queue.rs b/tests/queue.rs index e511612..438fa23 100644 --- a/tests/queue.rs +++ b/tests/queue.rs | |||
| @@ -549,7 +549,10 @@ fn test_shuffle_all_then_none_exact_scenario() { | |||
| 549 | q.set_position(3); | 549 | q.set_position(3); |
| 550 | 550 | ||
| 551 | let current_before = q.current().unwrap().path.clone(); | 551 | let current_before = q.current().unwrap().path.clone(); |
| 552 | println!("Current before restore: {:?}", current_before.file_name().unwrap()); | 552 | println!( |
| 553 | "Current before restore: {:?}", | ||
| 554 | current_before.file_name().unwrap() | ||
| 555 | ); | ||
| 553 | println!("Position: {}", q.position()); | 556 | println!("Position: {}", q.position()); |
| 554 | 557 | ||
| 555 | // Now shuffle none | 558 | // Now shuffle none |
| @@ -557,7 +560,11 @@ fn test_shuffle_all_then_none_exact_scenario() { | |||
| 557 | 560 | ||
| 558 | println!("After restore:"); | 561 | println!("After restore:"); |
| 559 | for i in 0..q.len() { | 562 | for i in 0..q.len() { |
| 560 | println!(" {}: {:?}", i+1, q.track_at(i).unwrap().path.file_name().unwrap()); | 563 | println!( |
| 564 | " {}: {:?}", | ||
| 565 | i + 1, | ||
| 566 | q.track_at(i).unwrap().path.file_name().unwrap() | ||
| 567 | ); | ||
| 561 | } | 568 | } |
| 562 | 569 | ||
| 563 | // After restore: | 570 | // After restore: |
| @@ -567,7 +574,10 @@ fn test_shuffle_all_then_none_exact_scenario() { | |||
| 567 | assert_eq!(q.position(), 3, "position should stay same"); | 574 | assert_eq!(q.position(), 3, "position should stay same"); |
| 568 | 575 | ||
| 569 | let current_after = q.current().unwrap().path.clone(); | 576 | let current_after = q.current().unwrap().path.clone(); |
| 570 | assert_eq!(current_before, current_after, "current track should stay same"); | 577 | assert_eq!( |
| 578 | current_before, current_after, | ||
| 579 | "current track should stay same" | ||
| 580 | ); | ||
| 571 | } | 581 | } |
| 572 | 582 | ||
| 573 | #[test] | 583 | #[test] |
| @@ -597,10 +607,17 @@ fn test_shuffle_restore_simulation() { | |||
| 597 | 607 | ||
| 598 | println!("After 4 next() calls:"); | 608 | println!("After 4 next() calls:"); |
| 599 | println!(" Position: {}", q.position()); | 609 | println!(" Position: {}", q.position()); |
| 600 | println!(" Current: {:?}", q.current().unwrap().path.file_name().unwrap()); | 610 | println!( |
| 611 | " Current: {:?}", | ||
| 612 | q.current().unwrap().path.file_name().unwrap() | ||
| 613 | ); | ||
| 601 | println!(" Tracks:"); | 614 | println!(" Tracks:"); |
| 602 | for i in 0..q.len() { | 615 | for i in 0..q.len() { |
| 603 | println!(" {}: {:?}", i, q.track_at(i).unwrap().path.file_name().unwrap()); | 616 | println!( |
| 617 | " {}: {:?}", | ||
| 618 | i, | ||
| 619 | q.track_at(i).unwrap().path.file_name().unwrap() | ||
| 620 | ); | ||
| 604 | } | 621 | } |
| 605 | 622 | ||
| 606 | let current_path = q.current().unwrap().path.clone(); | 623 | let current_path = q.current().unwrap().path.clone(); |
| @@ -611,20 +628,35 @@ fn test_shuffle_restore_simulation() { | |||
| 611 | 628 | ||
| 612 | println!("After shuffle none:"); | 629 | println!("After shuffle none:"); |
| 613 | println!(" Position: {}", q.position()); | 630 | println!(" Position: {}", q.position()); |
| 614 | println!(" Current: {:?}", q.current().unwrap().path.file_name().unwrap()); | 631 | println!( |
| 632 | " Current: {:?}", | ||
| 633 | q.current().unwrap().path.file_name().unwrap() | ||
| 634 | ); | ||
| 615 | println!(" Tracks:"); | 635 | println!(" Tracks:"); |
| 616 | for i in 0..q.len() { | 636 | for i in 0..q.len() { |
| 617 | println!(" {}: {:?}", i, q.track_at(i).unwrap().path.file_name().unwrap()); | 637 | println!( |
| 638 | " {}: {:?}", | ||
| 639 | i, | ||
| 640 | q.track_at(i).unwrap().path.file_name().unwrap() | ||
| 641 | ); | ||
| 618 | } | 642 | } |
| 619 | 643 | ||
| 620 | // Assertions | 644 | // Assertions |
| 621 | assert_eq!(q.position(), pos_before, "position should be preserved"); | 645 | assert_eq!(q.position(), pos_before, "position should be preserved"); |
| 622 | assert_eq!(q.current().unwrap().path, current_path, "current track should be preserved"); | 646 | assert_eq!( |
| 647 | q.current().unwrap().path, | ||
| 648 | current_path, | ||
| 649 | "current track should be preserved" | ||
| 650 | ); | ||
| 623 | 651 | ||
| 624 | // The key requirement: tracks before current position should remain in played order | 652 | // The key requirement: tracks before current position should remain in played order |
| 625 | // and tracks after should be remaining from original in original order | 653 | // and tracks after should be remaining from original in original order |
| 626 | let pos = q.position(); | 654 | let pos = q.position(); |
| 627 | for i in 0..pos { | 655 | for i in 0..pos { |
| 628 | println!("Played track {}: {:?}", i, q.track_at(i).unwrap().path.file_name().unwrap()); | 656 | println!( |
| 657 | "Played track {}: {:?}", | ||
| 658 | i, | ||
| 659 | q.track_at(i).unwrap().path.file_name().unwrap() | ||
| 660 | ); | ||
| 629 | } | 661 | } |
| 630 | } | 662 | } |
