Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- Bug preventing any type of playback due to spotify API changes.
- Bug preventing retrieval of new song metadata from spotify.
## [Unreleased]

### Changed

### Added

## [1.3.0]

Expand Down
2 changes: 2 additions & 0 deletions doc/users.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ playback depending on your desktop environment settings. Have a look at the
| <kbd>[</kbd> | Decrease volume by 5%. |
| <kbd>]</kbd> | Increase volume by 5%. |
| <kbd>R</kbd> | Toggle _Repeat_ mode. |
| <kbd>Shift</kbd>+<kbd>R</kbd> | Reverse the current playlist order. |
| <kbd>Z</kbd> | Toggle _Shuffle_ state. |

### Context Menus
Expand Down Expand Up @@ -179,6 +180,7 @@ Note: \<FOO\> - mandatory arg; [BAR] - optional arg
| `share` \<ITEM\> | Copy a shareable URL of the item to the system clipboard. Requires the `share_clipboard` feature.<br/>\* Valid values for ITEM: `selected`, `current` |
| `newplaylist` \<NAME\> | Create a new playlist. |
| `sort` \<SORT_KEY\> [SORT_DIRECTION] | Sort a playlist.<br/>\* Valid values for SORT_KEY: `title`, `album`, `artist`, `duration`, `added`<br/>\* Valid values for SORT_DIRECTION: `ascending` (default; aliases: `a`, `asc`), `descending` (aliases: `d`, `desc`) |
| `reverse` | Reverse the current playlist order and clear any stored sort preferences for that playlist. |
| `exec` \<CMD\> | Execute a command in the system shell.<br/>\* Command output is printed to the terminal, so redirection (`2> /dev/null`) may be necessary. |
| `noop` | Do nothing. Useful for disabling default keybindings. See [custom keybindings](#custom-keybindings). |
| `reload` | Reload the configuration from disk. See [Configuration](#configuration). |
Expand Down
4 changes: 4 additions & 0 deletions src/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ pub enum Command {
Insert(InsertSource),
NewPlaylist(String),
Sort(SortKey, SortDirection),
Reverse,
Logout,
ShowRecommendations(TargetMode),
Redraw,
Expand Down Expand Up @@ -226,6 +227,7 @@ impl fmt::Display for Command {
| Self::Help
| Self::ReloadConfig
| Self::Noop
| Self::Reverse
| Self::Logout
| Self::Reconnect
| Self::Redraw => vec![],
Expand Down Expand Up @@ -277,6 +279,7 @@ impl Command {
Self::Insert(_) => "insert",
Self::NewPlaylist(_) => "newplaylist",
Self::Sort(_, _) => "sort",
Self::Reverse => "reverse",
Self::Logout => "logout",
Self::ShowRecommendations(_) => "similar",
Self::Redraw => "redraw",
Expand Down Expand Up @@ -767,6 +770,7 @@ pub fn parse(input: &str) -> Result<Vec<Command>, CommandParseError> {
}?;
Command::Sort(key, direction)
}
"reverse" => Command::Reverse,
"logout" => Command::Logout,
"similar" => {
let &target_mode_raw = args.first().ok_or(E::InsufficientArgs {
Expand Down
4 changes: 3 additions & 1 deletion src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,8 @@ impl CommandManager {
| Command::Jump(_)
| Command::Insert(_)
| Command::ShowRecommendations(_)
| Command::Sort(_, _) => Err(format!(
| Command::Sort(_, _)
| Command::Reverse => Err(format!(
"The command \"{}\" is unsupported in this view",
cmd.basename()
)),
Expand Down Expand Up @@ -459,6 +460,7 @@ impl CommandManager {
kb.insert("[".into(), vec![Command::VolumeDown(5)]);

kb.insert("r".into(), vec![Command::Repeat(None)]);
kb.insert("Shift+r".into(), vec![Command::Reverse]);
kb.insert("z".into(), vec![Command::Shuffle(None)]);

#[cfg(feature = "share_clipboard")]
Expand Down
11 changes: 11 additions & 0 deletions src/model/playlist.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,17 @@ impl Playlist {
}
}

pub fn reverse(&mut self, library: &Library) {
if let Some(tracks) = &mut self.tracks {
tracks.reverse();
}

// Clear any stored sort order for this playlist
library.cfg.with_state_mut(|state| {
state.playlist_orders.remove(&self.id);
});
}

pub fn sort(&mut self, key: &SortKey, direction: &SortDirection) {
fn compare_artists(a: &[String], b: &[String]) -> Ordering {
let sanitize_artists_name = |x: &[String]| -> Vec<String> {
Expand Down
17 changes: 17 additions & 0 deletions src/queue.rs
Original file line number Diff line number Diff line change
Expand Up @@ -458,6 +458,23 @@ impl Queue {
}
}

/// Reverse the current queue order.
pub fn reverse(&self) {
let mut queue = self.queue.write().unwrap();
queue.reverse();

// If we have a current track, update its index to the new position
let mut current = self.current_track.write().unwrap();
if let Some(index) = *current {
let new_index = queue.len().saturating_sub(1).saturating_sub(index);
*current = Some(new_index);
Copy link

Copilot AI Aug 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The calculation for the new index after reversing has a logical error. For a queue of length n, when reversing, the element at index i should move to index n - 1 - i. The current calculation queue.len().saturating_sub(1).saturating_sub(index) is equivalent to (n - 1) - index, which is correct. However, this will panic if index >= queue.len(), and saturating_sub won't help in that case. Consider adding a bounds check: if index < queue.len() { let new_index = queue.len() - 1 - index; *current = Some(new_index); }

Suggested change
*current = Some(new_index);
if index < queue.len() {
let new_index = queue.len() - 1 - index;
*current = Some(new_index);
}

Copilot uses AI. Check for mistakes.
}

// Clear random order since reversing changes the logical order
let mut random_order = self.random_order.write().unwrap();
*random_order = None;
}

/// Handle events that are specific to the queue.
pub fn handle_event(&self, event: QueueEvent) {
match event {
Expand Down
11 changes: 11 additions & 0 deletions src/ui/playlist.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,17 @@ impl ViewExt for PlaylistView {
return Ok(CommandResult::Consumed(None));
}

if let Command::Reverse = cmd {
self.playlist.reverse(&self.library);
let tracks = self.playlist.tracks.as_ref().unwrap_or(&Vec::new()).clone();
self.list = ListView::new(
Arc::new(RwLock::new(tracks)),
self.queue.clone(),
self.library.clone(),
);
return Ok(CommandResult::Consumed(None));
}

self.list.on_command(s, cmd)
}
}