| 1 | ;;; emms-playlist-mode.el --- Playlist mode for Emms. -*- lexical-binding: t; -*- |
| 2 | |
| 3 | ;; Copyright (C) 2005-2024 Free Software Foundation, Inc. |
| 4 | |
| 5 | ;; Author: Yoni Rabkin <yrk@gnu.org> |
| 6 | |
| 7 | ;; This file is part of EMMS. |
| 8 | |
| 9 | ;; EMMS is free software; you can redistribute it and/or modify |
| 10 | ;; it under the terms of the GNU General Public License as published by |
| 11 | ;; the Free Software Foundation; either version 3, or (at your option) |
| 12 | ;; any later version. |
| 13 | ;; |
| 14 | ;; EMMS is distributed in the hope that it will be useful, |
| 15 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 16 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 17 | ;; GNU General Public License for more details. |
| 18 | ;; |
| 19 | ;; You should have received a copy of the GNU General Public License |
| 20 | ;; along with EMMS; if not, write to the Free Software Foundation, |
| 21 | ;; Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. |
| 22 | |
| 23 | ;;; Commentary: |
| 24 | ;;; |
| 25 | ;; This is a method of displaying and manipulating the different Emms |
| 26 | ;; playlist buffers. |
| 27 | ;; |
| 28 | ;; Emms developer's motto: "When forcer says (require 'jump) we say |
| 29 | ;; (funcall #'jump height)" |
| 30 | |
| 31 | ;;; Code: |
| 32 | |
| 33 | |
| 34 | ;;; -------------------------------------------------------- |
| 35 | ;;; Variables |
| 36 | ;;; -------------------------------------------------------- |
| 37 | (require 'emms) |
| 38 | (condition-case nil |
| 39 | (require 'overlay) |
| 40 | (error nil)) |
| 41 | (require 'emms-source-playlist) |
| 42 | |
| 43 | (defvar emms-playlist-mode-hook nil |
| 44 | "Emms playlist mode hook.") |
| 45 | |
| 46 | (defvar emms-playlist-mode-selected-overlay nil |
| 47 | "Last selected track. Use for updating the display.") |
| 48 | |
| 49 | (defvar emms-playlist-mode-switched-buffer nil |
| 50 | "Last buffer visited before calling `emms-playlist-mode-switch-buffer'.") |
| 51 | |
| 52 | (defvar emms-playlist-mode-popup-enabled nil |
| 53 | "True when the playlist was called as a popup window.") |
| 54 | |
| 55 | (defvar emms-playlist-mode-kill-whole-line-p t |
| 56 | "When true line kills behave like a typical music player.") |
| 57 | |
| 58 | (make-variable-buffer-local |
| 59 | 'emms-playlist-mode-selected-overlay) |
| 60 | |
| 61 | (defgroup emms-playlist-mode nil |
| 62 | "*The Emacs Multimedia System playlist mode." |
| 63 | :prefix "emms-playlist-mode-" |
| 64 | :group 'emms) |
| 65 | |
| 66 | (defcustom emms-playlist-mode-open-playlists nil |
| 67 | "Determine whether to open playlists in a new EMMS buffer on RET. |
| 68 | This is useful if you have a master playlist buffer that is |
| 69 | composed of other playlists." |
| 70 | :type 'boolean) |
| 71 | |
| 72 | (defcustom emms-playlist-mode-window-width 25 |
| 73 | "Determine the width of the Emms popup window. |
| 74 | The value should a positive integer." |
| 75 | :type 'integer) |
| 76 | |
| 77 | (defcustom emms-playlist-mode-center-when-go nil |
| 78 | "Determine whether to center on the currently selected track. |
| 79 | This is true for every invocation of `emms-playlist-mode-go'." |
| 80 | :type 'boolean) |
| 81 | |
| 82 | |
| 83 | ;;; -------------------------------------------------------- |
| 84 | ;;; Faces |
| 85 | ;;; -------------------------------------------------------- |
| 86 | (defface emms-playlist-track-face |
| 87 | '((((class color) (background dark)) |
| 88 | (:foreground "DarkSeaGreen")) |
| 89 | (((class color) (background light)) |
| 90 | (:foreground "Blue")) |
| 91 | (((type tty) (class mono)) |
| 92 | (:inverse-video t)) |
| 93 | (t (:background "Blue"))) |
| 94 | "Face for the tracks in a playlist buffer.") |
| 95 | |
| 96 | (defface emms-playlist-selected-face |
| 97 | '((((class color) (background dark)) |
| 98 | (:foreground "SteelBlue3")) |
| 99 | (((class color) (background light)) |
| 100 | (:background "blue3" :foreground "white")) |
| 101 | (((type tty) (class mono)) |
| 102 | (:inverse-video t)) |
| 103 | (t (:background "blue3"))) |
| 104 | "Face for highlighting the selected track.") |
| 105 | |
| 106 | |
| 107 | ;;; -------------------------------------------------------- |
| 108 | ;;; Keys |
| 109 | ;;; -------------------------------------------------------- |
| 110 | (defvar emms-playlist-mode-map |
| 111 | (let ((map (make-sparse-keymap))) |
| 112 | (set-keymap-parent map text-mode-map) |
| 113 | (define-key map (kbd "C-x C-s") #'emms-playlist-save) |
| 114 | (define-key map (kbd "C-y") #'emms-playlist-mode-yank) |
| 115 | (define-key map (kbd "C-k") #'emms-playlist-mode-kill-track) |
| 116 | (define-key map (kbd "C-w") #'emms-playlist-mode-kill) |
| 117 | (define-key map (kbd "C-_") #'emms-playlist-mode-undo) |
| 118 | (define-key map (kbd "C-/") #'emms-playlist-mode-undo) |
| 119 | (define-key map (kbd "C-x u") #'emms-playlist-mode-undo) |
| 120 | (define-key map (kbd "C-n") #'next-line) |
| 121 | (define-key map (kbd "C-p") #'previous-line) |
| 122 | (define-key map (kbd "C-j") #'emms-playlist-mode-insert-newline) |
| 123 | (define-key map (kbd "C-i") #'emms-playlist-mode-shift-track-up) |
| 124 | (define-key map (kbd "C-o") #'emms-playlist-mode-shift-track-down) |
| 125 | (define-key map (kbd "M-y") #'emms-playlist-mode-yank-pop) |
| 126 | (define-key map (kbd "M-<") #'emms-playlist-mode-first) |
| 127 | (define-key map (kbd "M->") #'emms-playlist-mode-last) |
| 128 | (define-key map (kbd "M-n") #'emms-playlist-mode-next) |
| 129 | (define-key map (kbd "M-p") #'emms-playlist-mode-previous) |
| 130 | (define-key map (kbd "a") #'emms-playlist-mode-add-contents) |
| 131 | (define-key map (kbd "b") #'emms-playlist-set-playlist-buffer) |
| 132 | (define-key map (kbd "D") #'emms-playlist-mode-kill-track) |
| 133 | (define-key map (kbd "n") #'emms-next) |
| 134 | (define-key map (kbd "p") #'emms-previous) |
| 135 | (define-key map (kbd "SPC") #'scroll-up) |
| 136 | (define-key map (kbd ">") #'emms-seek-forward) |
| 137 | (define-key map (kbd "<") #'emms-seek-backward) |
| 138 | (define-key map (kbd "P") #'emms-pause) |
| 139 | (define-key map (kbd "s") #'emms-stop) |
| 140 | (define-key map (kbd "f") #'emms-show) |
| 141 | (define-key map (kbd "c") #'emms-playlist-mode-center-current) |
| 142 | (define-key map (kbd "q") #'emms-playlist-mode-bury-buffer) |
| 143 | (define-key map (kbd "K") #'emms-playlist-mode-current-kill) |
| 144 | (define-key map (kbd "?") #'describe-mode) |
| 145 | (define-key map (kbd "r") #'emms-random) |
| 146 | (define-key map (kbd "C") #'emms-playlist-clear) |
| 147 | (define-key map (kbd "d") #'emms-playlist-mode-goto-dired-at-point) |
| 148 | (define-key map (kbd "<mouse-2>") #'emms-playlist-mode-play-current-track) |
| 149 | (define-key map (kbd "RET") #'emms-playlist-mode-play-smart) |
| 150 | (define-key map (kbd "i") #'emms-playlist-playlist-insert-track) |
| 151 | map) |
| 152 | "Keymap for `emms-playlist-mode'.") |
| 153 | |
| 154 | (defmacro emms-playlist-mode-move-wrapper (name fun) |
| 155 | "Create a function NAME which is an `interactive' version of FUN. |
| 156 | |
| 157 | NAME should be a symbol. |
| 158 | FUN should be a function." |
| 159 | `(defun ,name () |
| 160 | ,(format "Interactive wrapper around `%s' for playlist-mode." |
| 161 | fun) |
| 162 | (interactive) |
| 163 | (,fun))) |
| 164 | |
| 165 | (emms-playlist-mode-move-wrapper emms-playlist-mode-first |
| 166 | emms-playlist-first) |
| 167 | |
| 168 | (emms-playlist-mode-move-wrapper emms-playlist-mode-select-next |
| 169 | emms-playlist-next) |
| 170 | |
| 171 | (emms-playlist-mode-move-wrapper emms-playlist-mode-select-previous |
| 172 | emms-playlist-previous) |
| 173 | |
| 174 | (defun emms-playlist-mode-bury-buffer () |
| 175 | "Wrapper around `bury-buffer' for popup windows." |
| 176 | (interactive) |
| 177 | (if emms-playlist-mode-popup-enabled |
| 178 | (unwind-protect |
| 179 | (delete-window) |
| 180 | (setq emms-playlist-mode-popup-enabled nil)) |
| 181 | (bury-buffer))) |
| 182 | |
| 183 | (defun emms-playlist-mode-current-kill () |
| 184 | "If the current buffer is an EMMS playlist buffer, kill it. |
| 185 | Otherwise, kill the current EMMS playlist buffer." |
| 186 | (interactive) |
| 187 | (if (and emms-playlist-buffer-p |
| 188 | (not (eq (current-buffer) emms-playlist-buffer))) |
| 189 | (kill-buffer (current-buffer)) |
| 190 | (emms-playlist-current-kill))) |
| 191 | |
| 192 | (defun emms-playlist-mode-last () |
| 193 | "Move to directly after the last track in the current buffer." |
| 194 | (interactive) |
| 195 | (emms-playlist-ensure-playlist-buffer) |
| 196 | (let ((last (condition-case nil |
| 197 | (save-excursion |
| 198 | (goto-char (point-max)) |
| 199 | (point)) |
| 200 | (error |
| 201 | nil)))) |
| 202 | (if last |
| 203 | (goto-char last) |
| 204 | (error "No last track")))) |
| 205 | |
| 206 | (defun emms-playlist-mode-center-current () |
| 207 | "Move point to the currently selected track." |
| 208 | (interactive) |
| 209 | (goto-char (if emms-playlist-mode-selected-overlay |
| 210 | (overlay-start emms-playlist-mode-selected-overlay) |
| 211 | (point-min)))) |
| 212 | |
| 213 | (defun emms-playlist-mode-play-current-track () |
| 214 | "Start playing track at point." |
| 215 | (interactive) |
| 216 | (emms-playlist-set-playlist-buffer (current-buffer)) |
| 217 | (unless (emms-playlist-track-at (point)) |
| 218 | (emms-playlist-next)) |
| 219 | (emms-playlist-select (point)) |
| 220 | (when emms-player-playing-p |
| 221 | (emms-stop)) |
| 222 | (emms-start)) |
| 223 | |
| 224 | (defun emms-playlist-mode-play-smart () |
| 225 | "Determine the best operation to take on the current track. |
| 226 | |
| 227 | If on a playlist, and `emms-playlist-mode-open-playlists' is |
| 228 | non-nil, load the playlist at point into a new buffer. |
| 229 | |
| 230 | Otherwise play the track immediately." |
| 231 | (interactive) |
| 232 | (save-excursion |
| 233 | ;; move to the start of the line, in case the point is on the \n, |
| 234 | ;; which isn't propertized |
| 235 | (emms-move-beginning-of-line nil) |
| 236 | (if (not emms-playlist-mode-open-playlists) |
| 237 | (emms-playlist-mode-play-current-track) |
| 238 | (unless (emms-playlist-track-at) |
| 239 | (emms-playlist-next)) |
| 240 | (let* ((track (emms-playlist-track-at)) |
| 241 | (name (emms-track-get track 'name)) |
| 242 | (type (emms-track-get track 'type))) |
| 243 | (if (or (eq type 'playlist) |
| 244 | (and (eq type 'file) |
| 245 | (string-match "\\.\\(m3u\\|pls\\)\\'" name))) |
| 246 | (emms-playlist-mode-load-playlist) |
| 247 | (emms-playlist-mode-play-current-track)))))) |
| 248 | |
| 249 | (defun emms-playlist-mode-switch-buffer () |
| 250 | "Switch to the playlist buffer and then switch back if called again. |
| 251 | |
| 252 | This function switches to the current Emms playlist buffer and |
| 253 | remembers the buffer switched from. When called again the |
| 254 | function switches back to the remembered buffer." |
| 255 | (interactive) |
| 256 | (if (eq (current-buffer) |
| 257 | emms-playlist-buffer) |
| 258 | (switch-to-buffer emms-playlist-mode-switched-buffer) |
| 259 | (setq emms-playlist-mode-switched-buffer (current-buffer)) |
| 260 | (switch-to-buffer emms-playlist-buffer))) |
| 261 | |
| 262 | (defun emms-playlist-mode-insert-newline () |
| 263 | "Insert a newline at point." |
| 264 | (interactive) |
| 265 | (emms-with-inhibit-read-only-t |
| 266 | (newline))) |
| 267 | |
| 268 | (defun emms-playlist-mode-undo () |
| 269 | "Wrapper around `undo'." |
| 270 | (interactive) |
| 271 | (emms-with-inhibit-read-only-t |
| 272 | (undo))) |
| 273 | |
| 274 | (defun emms-playlist-mode-add-after-current (dir) |
| 275 | "Insert tracks from directory tree DIR after current track." |
| 276 | (interactive (list |
| 277 | (emms-read-directory-name "Add directory after current track: " |
| 278 | (emms-source-file-directory-hint) |
| 279 | emms-source-file-default-directory |
| 280 | t))) |
| 281 | (let ((p (if emms-playlist-mode-selected-overlay |
| 282 | (overlay-start emms-playlist-mode-selected-overlay) |
| 283 | (error "no current track")))) |
| 284 | (goto-char p) |
| 285 | (forward-line 1) |
| 286 | (emms-insert-directory-tree dir))) |
| 287 | |
| 288 | (defun emms-playlist-mode-add-contents () |
| 289 | "Add files in the playlist at point to the current playlist buffer. |
| 290 | |
| 291 | If we are in the current playlist, make a new playlist buffer and |
| 292 | set it as current." |
| 293 | (interactive) |
| 294 | (save-excursion |
| 295 | (emms-move-beginning-of-line nil) |
| 296 | (unless (emms-playlist-track-at) |
| 297 | (emms-playlist-next)) |
| 298 | (let* ((track (emms-playlist-track-at)) |
| 299 | (name (emms-track-get track 'name)) |
| 300 | (type (emms-track-get track 'type)) |
| 301 | (playlist-p (or (eq type 'playlist) |
| 302 | (and (eq type 'file) |
| 303 | (save-match-data |
| 304 | (string-match "\\.\\(m3u\\|pls\\)\\'" |
| 305 | name)))))) |
| 306 | (emms-playlist-select (point)) |
| 307 | (unless (and (buffer-live-p emms-playlist-buffer) |
| 308 | (not (eq (current-buffer) emms-playlist-buffer))) |
| 309 | (setq emms-playlist-buffer |
| 310 | (emms-playlist-set-playlist-buffer (emms-playlist-new)))) |
| 311 | (with-current-emms-playlist |
| 312 | (goto-char (point-max)) |
| 313 | (when playlist-p |
| 314 | (insert (emms-track-force-description track) "\n")) |
| 315 | (let ((beg (point))) |
| 316 | (if playlist-p |
| 317 | (emms-add-playlist name) |
| 318 | (let ((func (intern (concat "emms-add-" (symbol-name type))))) |
| 319 | (if (functionp func) |
| 320 | (funcall func name) |
| 321 | ;; fallback |
| 322 | (emms-add-file name)))) |
| 323 | (when playlist-p |
| 324 | (goto-char (point-max)) |
| 325 | (while (progn |
| 326 | (forward-line -1) |
| 327 | (>= (point) beg)) |
| 328 | (insert " "))) |
| 329 | (goto-char (point-min)) |
| 330 | (message "Added %s" (symbol-name type))))))) |
| 331 | |
| 332 | (defun emms-playlist-mode-goto-dired-at-point (&optional other-window) |
| 333 | "Visit the track at point in a `dired' buffer. |
| 334 | |
| 335 | With a prefix arg, open the `dired' buffer in OTHER-WINDOW." |
| 336 | (interactive "P") |
| 337 | (let ((track (emms-playlist-track-at))) |
| 338 | (if track |
| 339 | (let ((name (emms-track-get track 'name)) |
| 340 | (type (emms-track-get track 'type))) |
| 341 | (if (eq type 'file) |
| 342 | (dired-jump other-window name) |
| 343 | (error "Can't visit this track type in Dired"))) |
| 344 | (error "No track at point")))) |
| 345 | |
| 346 | |
| 347 | ;;; -------------------------------------------------------- |
| 348 | ;;; Killing and yanking |
| 349 | ;;; -------------------------------------------------------- |
| 350 | (defun emms-playlist-mode-between-p (p a b) |
| 351 | "Return t if P is a point between points A and B." |
| 352 | (and (<= a p) |
| 353 | (<= p b))) |
| 354 | |
| 355 | ;; D |
| 356 | (defun emms-playlist-mode-kill-entire-track () |
| 357 | "Kill track at point, including newline." |
| 358 | (interactive) |
| 359 | (let ((kill-whole-line t)) |
| 360 | (emms-playlist-mode-kill-track))) |
| 361 | |
| 362 | ;; C-k |
| 363 | (defun emms-playlist-mode-kill-track () |
| 364 | "Kill track at point." |
| 365 | (interactive) |
| 366 | (emms-with-inhibit-read-only-t |
| 367 | (let ((track (emms-playlist-track-at))) |
| 368 | (when track |
| 369 | (when (and emms-player-playing-p |
| 370 | (emms-playlist-selected-track-at-p)) |
| 371 | (emms-stop) |
| 372 | (delete-overlay emms-playlist-mode-selected-overlay) |
| 373 | (setq emms-playlist-mode-selected-overlay nil))) |
| 374 | (let ((kill-whole-line emms-playlist-mode-kill-whole-line-p)) |
| 375 | (goto-char (line-beginning-position)) |
| 376 | (kill-line))))) |
| 377 | |
| 378 | ;; C-w |
| 379 | (defun emms-playlist-mode-kill () |
| 380 | "Kill from mark to point." |
| 381 | (interactive) |
| 382 | (emms-with-inhibit-read-only-t |
| 383 | ;; Are we killing the playing/selected track? |
| 384 | (when (and (markerp emms-playlist-selected-marker) |
| 385 | (emms-playlist-mode-between-p |
| 386 | (marker-position emms-playlist-selected-marker) |
| 387 | (region-beginning) |
| 388 | (region-end))) |
| 389 | (emms-stop) |
| 390 | (delete-overlay emms-playlist-mode-selected-overlay) |
| 391 | (setq emms-playlist-mode-selected-overlay nil)) |
| 392 | (kill-region (region-beginning) |
| 393 | (region-end)))) |
| 394 | |
| 395 | (defun emms-playlist-mode-correct-previous-yank () |
| 396 | "Fix the previous yank if needed." |
| 397 | (when (and (< (line-beginning-position) (point)) |
| 398 | (< (point) (line-end-position))) |
| 399 | (newline))) |
| 400 | |
| 401 | ;; C-y |
| 402 | (defun emms-playlist-mode-yank () |
| 403 | "Yank into the playlist buffer." |
| 404 | (interactive) |
| 405 | (emms-with-inhibit-read-only-t |
| 406 | (goto-char (line-beginning-position)) |
| 407 | (yank) |
| 408 | (emms-playlist-mode-correct-previous-yank))) |
| 409 | |
| 410 | ;; M-y |
| 411 | (defun emms-playlist-mode-yank-pop () |
| 412 | "Cycle through the kill-ring." |
| 413 | (interactive) |
| 414 | (emms-with-inhibit-read-only-t |
| 415 | (yank-pop nil) |
| 416 | (emms-playlist-mode-correct-previous-yank))) |
| 417 | |
| 418 | ;; Don't assume that the first track is at the top of the buffer (the |
| 419 | ;; same goes for the `emms-playlist-mode-track-below-p'.) |
| 420 | (defun emms-playlist-mode-track-above-p () |
| 421 | "Return t if there is a track above this one in the buffer." |
| 422 | (previous-property-change (line-beginning-position))) |
| 423 | |
| 424 | (defun emms-playlist-mode-track-below-p () |
| 425 | "Return t if there is a track below this one in the buffer." |
| 426 | (next-property-change (line-end-position))) |
| 427 | |
| 428 | ;; The need to avoid killing a playing track causes the code to be |
| 429 | ;; written in a bit of a convoluted manner. |
| 430 | (defun emms-playlist-mode-shift-track (num) |
| 431 | "Shift the track at point by one line. |
| 432 | |
| 433 | If NUM is 0 or a positive number, shift the track at point down |
| 434 | one line. Otherwise shift the track up by one line." |
| 435 | (emms-playlist-ensure-playlist-buffer) |
| 436 | (let ((track (emms-playlist-track-at (point))) |
| 437 | (dir (if (>= num 0) 1 -1))) |
| 438 | (when (not track) |
| 439 | (error "no track at point.")) |
| 440 | (when (and (< num 0) |
| 441 | (not (emms-playlist-mode-track-above-p))) |
| 442 | (error "already the first track")) |
| 443 | (when (and (> num 0) |
| 444 | (not (emms-playlist-mode-track-below-p))) |
| 445 | (error "already the last track")) |
| 446 | (forward-line dir) |
| 447 | (if (emms-playlist-selected-track-at-p) |
| 448 | (progn |
| 449 | (emms-playlist-mode-shift-track (if (= dir 1) -1 1)) |
| 450 | (forward-line (if (= dir 1) 1 -1))) |
| 451 | (let ((shift (emms-playlist-track-at (point)))) |
| 452 | (emms-playlist-mode-kill-track) |
| 453 | (when (not emms-playlist-mode-kill-whole-line-p) |
| 454 | (emms-with-inhibit-read-only-t |
| 455 | (kill-line))) |
| 456 | (forward-line (* dir -1)) |
| 457 | (emms-playlist-mode-insert-track shift) |
| 458 | (when (= dir -1) (forward-line -2)))))) |
| 459 | |
| 460 | (defun emms-playlist-mode-shift-track-down () |
| 461 | "Shift the track at point down one line." |
| 462 | (interactive) |
| 463 | (emms-playlist-mode-shift-track 0)) |
| 464 | |
| 465 | (defun emms-playlist-mode-shift-track-up () |
| 466 | "Shift the track at point up one line." |
| 467 | (interactive) |
| 468 | (emms-playlist-mode-shift-track -1)) |
| 469 | |
| 470 | |
| 471 | ;;; -------------------------------------------------------- |
| 472 | ;;; Overlay |
| 473 | ;;; -------------------------------------------------------- |
| 474 | (defun emms-playlist-mode-overlay-selected () |
| 475 | "Place an overlay over the currently selected track." |
| 476 | (when emms-playlist-selected-marker |
| 477 | (save-excursion |
| 478 | (goto-char emms-playlist-selected-marker) |
| 479 | (let ((reg (emms-property-region (point) 'emms-track))) |
| 480 | (if emms-playlist-mode-selected-overlay |
| 481 | (move-overlay emms-playlist-mode-selected-overlay |
| 482 | (car reg) |
| 483 | (cdr reg)) |
| 484 | (setq emms-playlist-mode-selected-overlay |
| 485 | (make-overlay (car reg) |
| 486 | (cdr reg) |
| 487 | nil t nil)) |
| 488 | (overlay-put emms-playlist-mode-selected-overlay |
| 489 | 'face 'emms-playlist-selected-face) |
| 490 | (overlay-put emms-playlist-mode-selected-overlay |
| 491 | 'evaporate t)))))) |
| 492 | |
| 493 | |
| 494 | ;;; -------------------------------------------------------- |
| 495 | ;;; Saving/Restoring |
| 496 | ;;; -------------------------------------------------------- |
| 497 | (defun emms-playlist-mode-open-buffer (filename) |
| 498 | "Opens a previously saved playlist buffer. |
| 499 | |
| 500 | It creates a buffer called \"filename\", and restores the contents |
| 501 | of the saved playlist inside." |
| 502 | (interactive "fFile: ") |
| 503 | (let* ((s) |
| 504 | (buffer (get-buffer-create filename)) |
| 505 | (name (buffer-name buffer))) |
| 506 | (with-current-buffer buffer |
| 507 | (emms-insert-file-contents filename) |
| 508 | (setq s (read (buffer-string)))) |
| 509 | (kill-buffer buffer) |
| 510 | (with-current-buffer (emms-playlist-new name) |
| 511 | (emms-with-inhibit-read-only-t |
| 512 | (insert s) |
| 513 | (goto-char (point-min)) |
| 514 | (emms-walk-tracks |
| 515 | (emms-playlist-update-track))) |
| 516 | (emms-playlist-first) |
| 517 | (emms-playlist-select (point)) |
| 518 | (switch-to-buffer (current-buffer))))) |
| 519 | |
| 520 | (defun emms-playlist-mode-load-playlist () |
| 521 | "Load the playlist into a new EMMS buffer. |
| 522 | This preserves the current EMMS buffer." |
| 523 | (interactive) |
| 524 | (let* ((track (emms-playlist-track-at)) |
| 525 | (name (emms-track-get track 'name))) |
| 526 | (emms-playlist-select (point)) |
| 527 | (run-hooks 'emms-player-stopped-hook) |
| 528 | (switch-to-buffer |
| 529 | (emms-playlist-set-playlist-buffer (emms-playlist-new))) |
| 530 | (emms-add-playlist name))) |
| 531 | |
| 532 | |
| 533 | ;;; -------------------------------------------------------- |
| 534 | ;;; Local functions |
| 535 | ;;; -------------------------------------------------------- |
| 536 | (defun emms-playlist-mode-insert-track (track &optional no-newline) |
| 537 | "Insert the description of TRACK at point. |
| 538 | When NO-NEWLINE is non-nil, do not insert a newline after the track." |
| 539 | (emms-playlist-ensure-playlist-buffer) |
| 540 | (emms-with-inhibit-read-only-t |
| 541 | (insert (emms-propertize (emms-track-force-description track) |
| 542 | 'emms-track track |
| 543 | 'face 'emms-playlist-track-face)) |
| 544 | (unless no-newline |
| 545 | (insert "\n")))) |
| 546 | |
| 547 | (defun emms-playlist-playlist-insert-track () |
| 548 | "Insert the track in playlist at point into the active playlist buffer." |
| 549 | (interactive) |
| 550 | (let ((track (emms-playlist-track-at))) |
| 551 | (with-current-emms-playlist |
| 552 | (goto-char (point-max)) |
| 553 | (emms-playlist-insert-track track)))) |
| 554 | |
| 555 | (defun emms-playlist-mode-update-track-function () |
| 556 | "Update the track display at point." |
| 557 | (emms-playlist-ensure-playlist-buffer) |
| 558 | (emms-with-inhibit-read-only-t |
| 559 | (let ((track-region (emms-property-region (point) |
| 560 | 'emms-track)) |
| 561 | (track (get-text-property (point) |
| 562 | 'emms-track)) |
| 563 | (selectedp (emms-playlist-selected-track-at-p))) |
| 564 | (save-excursion |
| 565 | (delete-region (car track-region) |
| 566 | (cdr track-region)) |
| 567 | (when selectedp |
| 568 | (delete-overlay emms-playlist-mode-selected-overlay) |
| 569 | (setq emms-playlist-mode-selected-overlay nil)) |
| 570 | (emms-playlist-mode-insert-track track t)) |
| 571 | (when selectedp |
| 572 | (emms-playlist-select (point)))))) |
| 573 | |
| 574 | |
| 575 | ;;; -------------------------------------------------------- |
| 576 | ;;; Entry |
| 577 | ;;; -------------------------------------------------------- |
| 578 | (defun emms-playlist-mode-go () |
| 579 | "Switch to the current emms-playlist buffer and use emms-playlist-mode." |
| 580 | (interactive) |
| 581 | (if (or (null emms-playlist-buffer) |
| 582 | (not (buffer-live-p emms-playlist-buffer))) |
| 583 | (error "No current Emms buffer") |
| 584 | (switch-to-buffer emms-playlist-buffer) |
| 585 | (when (and (not (member major-mode '(emms-playlist-mode emms-mark-mode))) |
| 586 | emms-playlist-buffer-p) |
| 587 | (emms-playlist-mode)) |
| 588 | (when emms-playlist-mode-center-when-go |
| 589 | (emms-playlist-mode-center-current)))) |
| 590 | |
| 591 | (defun emms () |
| 592 | "Switch to the current emms-playlist buffer, use |
| 593 | emms-playlist-mode and query for a directory tree to add to the |
| 594 | playlist." |
| 595 | (interactive) |
| 596 | (if (or (null emms-playlist-buffer) |
| 597 | (not (buffer-live-p emms-playlist-buffer))) |
| 598 | (call-interactively 'emms-add-directory)) |
| 599 | (emms-playlist-mode-go)) |
| 600 | |
| 601 | (defun emms-playlist-mode-go-popup (&optional window-width) |
| 602 | "Popup emms-playlist buffer as a side window. |
| 603 | |
| 604 | Default value for WINDOW-WIDTH is `emms-playlist-mode-window-width'. |
| 605 | WINDOW-WIDTH should be a positive integer." |
| 606 | (interactive) |
| 607 | (setq emms-playlist-mode-window-width |
| 608 | (round (or window-width emms-playlist-mode-window-width))) |
| 609 | (split-window-horizontally (- emms-playlist-mode-window-width)) |
| 610 | (other-window 1) |
| 611 | (emms-playlist-mode-go) |
| 612 | (setq emms-playlist-mode-popup-enabled t)) |
| 613 | |
| 614 | (defun emms-playlist-mode-next (arg) |
| 615 | "Navigate between playlists." |
| 616 | (interactive "p") |
| 617 | (let ((playlists (emms-playlist-buffer-list)) |
| 618 | bufs idx) |
| 619 | (if playlists |
| 620 | ;; if not in playlist mode, switch to emms-playlist-buffer |
| 621 | (if (not (member (current-buffer) playlists)) |
| 622 | (switch-to-buffer (if (and emms-playlist-buffer |
| 623 | (buffer-live-p emms-playlist-buffer)) |
| 624 | emms-playlist-buffer |
| 625 | (car playlists))) |
| 626 | (setq bufs (member (current-buffer) playlists)) |
| 627 | (setq idx |
| 628 | (+ (- (length playlists) (length bufs)) |
| 629 | (if (> arg 0) 1 -1))) |
| 630 | (switch-to-buffer (nth (mod idx (length playlists)) playlists))) |
| 631 | (message "No playlist found!")))) |
| 632 | (defun emms-playlist-mode-previous (arg) |
| 633 | (interactive "p") |
| 634 | (emms-playlist-mode-next (- arg))) |
| 635 | |
| 636 | (defun emms-playlist-mode-startup () |
| 637 | "Instigate emms-playlist-mode on the current buffer." |
| 638 | ;; when there is neither a current emms track or a playing one... |
| 639 | (when (not (or emms-playlist-selected-marker |
| 640 | emms-player-playing-p)) |
| 641 | ;; ...then stop the player. |
| 642 | (emms-stop) |
| 643 | ;; why select the first track? |
| 644 | (when emms-playlist-buffer-p |
| 645 | (emms-playlist-select-first))) |
| 646 | ;; when there is a selected track. |
| 647 | (when emms-playlist-selected-marker |
| 648 | (emms-playlist-mode-overlay-selected)) |
| 649 | (emms-with-inhibit-read-only-t |
| 650 | (add-text-properties (point-min) |
| 651 | (point-max) |
| 652 | '(face emms-playlist-track-face))) |
| 653 | (setq buffer-read-only t) |
| 654 | (setq truncate-lines t) |
| 655 | (setq buffer-undo-list nil)) |
| 656 | |
| 657 | ;;;###autoload |
| 658 | (defun emms-playlist-mode () ;FIXME: Use `define-derived-mode'. |
| 659 | "A major mode for Emms playlists. |
| 660 | \\{emms-playlist-mode-map}" |
| 661 | (interactive) |
| 662 | (let ((val emms-playlist-buffer-p)) |
| 663 | (kill-all-local-variables) |
| 664 | (setq emms-playlist-buffer-p val)) |
| 665 | |
| 666 | (use-local-map emms-playlist-mode-map) |
| 667 | (setq major-mode 'emms-playlist-mode |
| 668 | mode-name "EMMS") |
| 669 | |
| 670 | (setq emms-playlist-insert-track-function |
| 671 | #'emms-playlist-mode-insert-track) |
| 672 | (setq emms-playlist-update-track-function |
| 673 | #'emms-playlist-mode-update-track-function) |
| 674 | (add-hook 'emms-playlist-selection-changed-hook |
| 675 | #'emms-playlist-mode-overlay-selected) |
| 676 | |
| 677 | (emms-playlist-mode-startup) |
| 678 | |
| 679 | (run-hooks 'emms-playlist-mode-hook)) |
| 680 | |
| 681 | |
| 682 | (provide 'emms-playlist-mode) |
| 683 | |
| 684 | |
| 685 | ;;; emms-playlist-mode.el ends here |