Skip to content

Add rename playlist to the playlist manager#239

Open
abdimed wants to merge 1 commit into
bjarneo:mainfrom
abdimed:feat/rename-playlist
Open

Add rename playlist to the playlist manager#239
abdimed wants to merge 1 commit into
bjarneo:mainfrom
abdimed:feat/rename-playlist

Conversation

@abdimed
Copy link
Copy Markdown

@abdimed abdimed commented May 20, 2026

Summary

Adds a rename playlist action to the TUI playlist manager overlay. Press r on any playlist in the manager to rename it. The input is pre-populated with the current name. Enter commits, Esc cancels. The reserved "Recently Played" history playlist cannot be renamed.

Screenshots / video

https://github.com/user-attachments/assets/2efe4a6c-bb0d-4750-ab5a-9b468666599f
Screenshot_20260520_160204-1

How to test

  1. Launch cliamp and open the playlist manager with p
  2. Navigate to any playlist with ↑/↓
  3. Press r a rename screen appears pre-filled with the current name
  4. Edit the name and press Enter
  5. The playlist is renamed and the list refreshes

Checklist

  • make check passes
  • docs/ and site/index.html updated for user-facing changes

Summary by CodeRabbit

  • New Features

    • Added playlist rename functionality accessible via 'r' keybinding in the playlist manager
    • Dedicated rename input screen with text editing and Backspace/Space support
    • Confirm renaming with Enter; cancel with Esc
    • Reserved playlists protected from renaming
  • Documentation

    • Updated keybinding documentation for the new rename shortcut

Review Change Stack

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 20, 2026

📝 Walkthrough

Walkthrough

This PR introduces playlist renaming capability to the music player. The feature adds a provider interface contract, backend implementation with validation, UI state tracking, input handling via the r key, overlay rendering, and keybinding documentation across provider and UI layers.

Changes

Playlist Rename Feature

Layer / File(s) Summary
Provider interface and implementation
provider/interfaces.go, external/local/provider.go
PlaylistRenamer interface defines RenamePlaylist(oldName, newName string) error. The local provider implements it with validation: blocks renaming the reserved "Recently Played" playlist, validates names via safePath, checks destination does not exist, and performs os.Rename on the TOML file.
UI state structure for rename
ui/model/state.go, ui/model/model.go
plManagerState adds renameOldName and renameName fields. A new plMgrScreenRename enum constant extends the screen type set to track the rename input state.
Rename input handling
ui/model/keys.go
The r key in playlist list initializes rename state and switches to rename screen. New handlePlMgrRenameKey processes rename input: Escape cancels, Enter commits via provider.PlaylistRenamer (when new name is non-empty and different), Backspace/text edits the name. Quick provider switching is blocked on rename screen. Screen dispatcher routes plMgrScreenRename to the rename handler.
Rename overlay rendering and setup
ui/model/view_overlays.go, ui/model/overlays.go
New renderPlMgrRename renders the rename overlay showing current and new name with Enter/Esc help. Help text adds r shortcut label. Manager overlay reset clears renameOldName and renameName on open.
Keybinding documentation
docs/keybindings.md, site/index.html
Documents the r key as "rename the playlist" in both markdown and HTML keybinding references.

🎯 2 (Simple) | ⏱️ ~10 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Add rename playlist to the playlist manager' clearly and directly describes the main change: adding a rename playlist feature to the playlist manager overlay.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@external/local/provider.go`:
- Around line 363-366: The code checks os.Stat(newPath) but ignores errors other
than "nil" and then calls os.Rename without wrapping errors; change the logic so
that after calling os.Stat(newPath) you return a wrapped error if stat failed
for any reason other than os.IsNotExist(err) (e.g. return fmt.Errorf("stat %q:
%w", newPath, err)); keep the existing check that returns fmt.Errorf("playlist
%q already exists", newName) when stat == nil; then call os.Rename(oldPath,
newPath) and if it returns an error wrap and return it (e.g. return
fmt.Errorf("rename %q -> %q: %w", oldPath, newPath, err)). Ensure you reference
newPath, newName, oldPath, os.Stat and os.Rename when making the changes.

In `@ui/model/keys.go`:
- Around line 1761-1764: After a successful rename in the else branch, update
the active playlist reference so future name-based ops work: set
m.loadedPlaylist to the new name (use newName) before calling
m.plMgrRefreshList(); ensure you only overwrite m.loadedPlaylist when the
renamed playlist was the currently loaded one (compare to
m.plManager.renameOldName if necessary) so other state isn't clobbered.
- Around line 1474-1481: In the "r" key handler branch, prevent entering rename
mode for the reserved history playlist by checking the real playlist before
setting rename state: after computing realIdx with
m.plMgrPlaylistRealIndex(m.plManager.cursor) and retrieving pl :=
m.plManager.playlists[realIdx], return/do nothing if that playlist is the
reserved history (e.g. pl.IsReserved || pl.Type == PlaylistTypeHistory ||
pl.Name == reservedHistoryName depending on how the playlist marks it); only set
m.plManager.renameOldName, m.plManager.renameName and m.plManager.screen =
plMgrScreenRename when the playlist is not the reserved history.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 01b8846f-118a-490a-b861-f4caeb23e026

📥 Commits

Reviewing files that changed from the base of the PR and between 94ba80c and ed2da87.

📒 Files selected for processing (9)
  • docs/keybindings.md
  • external/local/provider.go
  • provider/interfaces.go
  • site/index.html
  • ui/model/keys.go
  • ui/model/model.go
  • ui/model/overlays.go
  • ui/model/state.go
  • ui/model/view_overlays.go

Comment on lines +363 to +366
if _, err := os.Stat(newPath); err == nil {
return fmt.Errorf("playlist %q already exists", newName)
}
return os.Rename(oldPath, newPath)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Handle unexpected destination stat errors and wrap rename failures.

If os.Stat(newPath) fails for reasons other than non-existence, the code currently continues and can surface a misleading error later. Also, os.Rename should be wrapped with context.

Proposed fix
 	if _, err := os.Stat(newPath); err == nil {
 		return fmt.Errorf("playlist %q already exists", newName)
+	} else if !errors.Is(err, os.ErrNotExist) {
+		return fmt.Errorf("stat destination playlist %q: %w", newName, err)
 	}
-	return os.Rename(oldPath, newPath)
+	if err := os.Rename(oldPath, newPath); err != nil {
+		return fmt.Errorf("rename playlist %q to %q: %w", oldName, newName, err)
+	}
+	return nil
 }

As per coding guidelines, "Error handling must wrap errors with fmt.Errorf("context: %w", err)."

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if _, err := os.Stat(newPath); err == nil {
return fmt.Errorf("playlist %q already exists", newName)
}
return os.Rename(oldPath, newPath)
if _, err := os.Stat(newPath); err == nil {
return fmt.Errorf("playlist %q already exists", newName)
} else if !errors.Is(err, os.ErrNotExist) {
return fmt.Errorf("stat destination playlist %q: %w", newName, err)
}
if err := os.Rename(oldPath, newPath); err != nil {
return fmt.Errorf("rename playlist %q to %q: %w", oldName, newName, err)
}
return nil
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@external/local/provider.go` around lines 363 - 366, The code checks
os.Stat(newPath) but ignores errors other than "nil" and then calls os.Rename
without wrapping errors; change the logic so that after calling os.Stat(newPath)
you return a wrapped error if stat failed for any reason other than
os.IsNotExist(err) (e.g. return fmt.Errorf("stat %q: %w", newPath, err)); keep
the existing check that returns fmt.Errorf("playlist %q already exists",
newName) when stat == nil; then call os.Rename(oldPath, newPath) and if it
returns an error wrap and return it (e.g. return fmt.Errorf("rename %q -> %q:
%w", oldPath, newPath, err)). Ensure you reference newPath, newName, oldPath,
os.Stat and os.Rename when making the changes.

Comment thread ui/model/keys.go
Comment on lines +1474 to +1481
case "r":
realIdx := m.plMgrPlaylistRealIndex(m.plManager.cursor)
if realIdx >= 0 {
name := m.plManager.playlists[realIdx].Name
m.plManager.renameOldName = name
m.plManager.renameName = name
m.plManager.screen = plMgrScreenRename
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Prevent entering rename mode for the reserved history playlist.

The current flow opens the rename screen for the reserved playlist and only fails on submit. This should be blocked at key handling time to match the feature contract.

Proposed fix
 	case "r":
 		realIdx := m.plMgrPlaylistRealIndex(m.plManager.cursor)
 		if realIdx >= 0 {
 			name := m.plManager.playlists[realIdx].Name
+			if name == history.PlaylistName {
+				m.status.Show("Recently Played cannot be renamed", statusTTLDefault)
+				return nil
+			}
 			m.plManager.renameOldName = name
 			m.plManager.renameName = name
 			m.plManager.screen = plMgrScreenRename
 		}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
case "r":
realIdx := m.plMgrPlaylistRealIndex(m.plManager.cursor)
if realIdx >= 0 {
name := m.plManager.playlists[realIdx].Name
m.plManager.renameOldName = name
m.plManager.renameName = name
m.plManager.screen = plMgrScreenRename
}
case "r":
realIdx := m.plMgrPlaylistRealIndex(m.plManager.cursor)
if realIdx >= 0 {
name := m.plManager.playlists[realIdx].Name
if name == history.PlaylistName {
m.status.Show("Recently Played cannot be renamed", statusTTLDefault)
return nil
}
m.plManager.renameOldName = name
m.plManager.renameName = name
m.plManager.screen = plMgrScreenRename
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ui/model/keys.go` around lines 1474 - 1481, In the "r" key handler branch,
prevent entering rename mode for the reserved history playlist by checking the
real playlist before setting rename state: after computing realIdx with
m.plMgrPlaylistRealIndex(m.plManager.cursor) and retrieving pl :=
m.plManager.playlists[realIdx], return/do nothing if that playlist is the
reserved history (e.g. pl.IsReserved || pl.Type == PlaylistTypeHistory ||
pl.Name == reservedHistoryName depending on how the playlist marks it); only set
m.plManager.renameOldName, m.plManager.renameName and m.plManager.screen =
plMgrScreenRename when the playlist is not the reserved history.

Comment thread ui/model/keys.go
Comment on lines +1761 to +1764
} else {
m.status.Showf(statusTTLDefault, "Renamed %q to %q", m.plManager.renameOldName, newName)
m.plMgrRefreshList()
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Update loadedPlaylist after a successful rename.

After renaming, m.loadedPlaylist can still point to the old name, which breaks later name-based operations for the active local playlist.

Proposed fix
 				} else {
 					m.status.Showf(statusTTLDefault, "Renamed %q to %q", m.plManager.renameOldName, newName)
+					if m.loadedPlaylist == m.plManager.renameOldName {
+						m.loadedPlaylist = newName
+					}
 					m.plMgrRefreshList()
 				}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
} else {
m.status.Showf(statusTTLDefault, "Renamed %q to %q", m.plManager.renameOldName, newName)
m.plMgrRefreshList()
}
} else {
m.status.Showf(statusTTLDefault, "Renamed %q to %q", m.plManager.renameOldName, newName)
if m.loadedPlaylist == m.plManager.renameOldName {
m.loadedPlaylist = newName
}
m.plMgrRefreshList()
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ui/model/keys.go` around lines 1761 - 1764, After a successful rename in the
else branch, update the active playlist reference so future name-based ops work:
set m.loadedPlaylist to the new name (use newName) before calling
m.plMgrRefreshList(); ensure you only overwrite m.loadedPlaylist when the
renamed playlist was the currently loaded one (compare to
m.plManager.renameOldName if necessary) so other state isn't clobbered.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant