Browse Source

Refactor everything around navigel

The code is now much simpler. Hopefully, all features are still there
but time will tell. At least, the code is now much simpler.
master
Damien Cassou 7 months ago
parent
commit
4b27915f9e
Signed by: DamienCassou <damien@cassou.me> GPG Key ID: B68746238E59B548
8 changed files with 370 additions and 605 deletions
  1. +1
    -1
      Makefile
  2. +17
    -24
      README.org
  3. +68
    -90
      mpdel-core.el
  4. +0
    -213
      mpdel-nav.el
  5. +72
    -192
      mpdel-playlist.el
  6. +62
    -8
      mpdel-song.el
  7. +144
    -0
      mpdel-tablist.el
  8. +6
    -77
      mpdel.el

+ 1
- 1
Makefile View File

@@ -1,4 +1,4 @@
ELPA_DEPENDENCIES=package-lint libmpdel
ELPA_DEPENDENCIES=package-lint libmpdel navigel tablist

ELPA_ARCHIVES=melpa



+ 17
- 24
README.org View File

@@ -77,7 +77,6 @@ is active:
| ~C-x Z l~ | open the current playlist |
| ~C-x Z L~ | open a stored playlist |
| ~C-x Z n~ | navigate your database from artists |
| ~C-x Z N~ | navigate your database from stored playlists |
| ~C-x Z v~ | view current song |
| ~C-x Z s r~ | search songs by artist name |
| ~C-x Z s l~ | search songs by album name |
@@ -121,31 +120,25 @@ albums. On the contrary, press ~^~ to go back to the item's parent
here to add albums to a playlist or dive into an album's songs. Diving
into a song displays some information about it.

You can also start a navigator on your stored playlists with ~C-x Z N~
(or just ~N~) but that's probably less useful than opening a stored
playlist buffer (with ~C-x Z L~ or just ~L~).

There is just one navigator buffer available at any time and its name
is ~*MPDEL Navigator*~.

*** Playlists

Playlist buffers display either the current playlist (~C-x Z l~ or
just ~l~) or any stored playlist (~C-x Z L~ or just ~L~). A playlist
buffer lists songs and can be edited.

Here is a list of keybindings available for playlist buffers:
Here is a list of keybindings available for the current playlist
buffer:

| *Binding* | *Action* |
|-----------+--------------------------------------------|
| ~C-x C-s~ | save current playlist in a new one |
| ~k~ | remove selected song(s) from playlist |
| ~M-up~ | move selected song(s) up in the playlist |
| ~M-down~ | move selected song(s) down in the playlist |
| *Binding* | *Action* |
|-----------+------------------------------------------|
| ~C-x C-s~ | save current playlist in a new one |
| ~m~ | mark the song at point |
| ~k~ | remove marked song(s) from the playlist |
| ~M-up~ | move marked song(s) up in the playlist |
| ~M-down~ | move marked song(s) down in the playlist |

When a command acts on the selected song(s) (such as ~k~), the song at
point will be used if there is no active region and all songs of the
region will be used otherwise.
When a command acts on the marked song(s) (such as ~k~), the song at
point is used if there is no marked song.

Playlist buffers are refreshed automatically when the MPD server
refreshes them.
@@ -196,12 +189,12 @@ MPDel defines several faces to customize the playlists:
| *Face name* | *Description* |
|------------------------------------+-----------------------|
| ~mpdel-playlist-current-song-face~ | currently-played song |
| ~mpdel-playlist-name-face~ | song names |
| ~mpdel-playlist-track-face~ | track numbers |
| ~mpdel-playlist-album-face~ | album names |
| ~mpdel-playlist-disk-face~ | disk numbers |
| ~mpdel-playlist-date-face~ | dates |
| ~mpdel-playlist-artist-face~ | artist names |
| ~mpdel-tablist-song-name-face~ | song names |
| ~mpdel-tablist-track-face~ | track numbers |
| ~mpdel-tablist-album-face~ | album names |
| ~mpdel-tablist-disk-face~ | disk numbers |
| ~mpdel-tablist-date-face~ | dates |
| ~mpdel-tablist-artist-face~ | artist names |

*** Hooks



+ 68
- 90
mpdel-core.el View File

@@ -27,96 +27,61 @@
;; user-interfaces.

;;; Code:
(require 'tabulated-list)

(require 'libmpdel)
(require 'navigel)

;;; Helper functions

(defun mpdel-core--points-in-region (start end)
"Return a list of points for lines between START and END."
(save-excursion
(let (points)
(goto-char start)
(while (and (<= (point) end) (< (point) (point-max)))
(push (line-beginning-position) points)
(forward-line 1))
(reverse points))))

(cl-defgeneric mpdel-core--entity-at-point (_pos _mode)
"Return entity at POS, nil if none.
MODE is the current buffer's major mode."
nil)

(cl-defmethod mpdel-core--entity-at-point (pos (_mode (derived-mode tabulated-list-mode)))
(tabulated-list-get-id pos))
;;; `navigel' general configuration

(defun mpdel-core-selected-entities ()
"Return entities within active region or at point.
Return nil if no entity is found."
(cond
((use-region-p)
(mapcar (lambda (pos) (mpdel-core--entity-at-point pos major-mode))
(mpdel-core--points-in-region (region-beginning) (region-end))))
((= (point) (point-max)) nil)
(t (let ((entity (mpdel-core--entity-at-point (point) major-mode)))
(when entity (list entity))))))

(defun mpdel-core-entity-at-point (&optional pos buffer)
"Return entity at POS in BUFFER.
Use point if POS is nil and use current buffer if BUFFER is nil."
(with-current-buffer (or buffer (current-buffer))
(mpdel-core--entity-at-point (or pos (point)) major-mode)))

(cl-defgeneric mpdel-core--open-entity (entity &optional target)
"Open buffer displaying information about ENTITY.

If TARGET is non-nil and is in buffer, move point to it.")

(defun mpdel-core-go-to-entity (entity &optional buffer)
"Move point to ENTITY in BUFFER, current one if nil.
Return non-nil if ENTITY is found, nil otherwise."
(with-current-buffer (or buffer (current-buffer))
(goto-char (point-min))
(while (and (not (= (point) (point-max)))
(not (libmpdel-equal (mpdel-core-entity-at-point) entity)))
(forward-line 1))
(not (= (point) (point-max)))))
(cl-defmethod navigel-name (entity &context (navigel-app mpdel))
(libmpdel-entity-name entity))

(cl-defmethod navigel-children (entity callback &context (navigel-app mpdel))
(libmpdel-list entity callback))

(cl-defmethod navigel-equal (entity1 entity2 &context (navigel-app mpdel))
(libmpdel-equal entity1 entity2))

(cl-defmethod navigel-parent (entity &context (navigel-app mpdel))
(libmpdel-entity-parent entity))

(defun mpdel-core-after-playlist-modification ()
"Called at the end of any playlist modification command."
(when (derived-mode-p 'tabulated-list-mode)
(unless (use-region-p)
(forward-line 1))
(setq deactivate-mark t)))
;;; Public functions

;;; Commands
(defun mpdel-core-open (entity &optional target)
"Open a buffer showing ENTITY.
If TARGET is non nil and visible on the buffer, move point to
it."
(let ((navigel-app 'mpdel))
(navigel-open entity target)))

(defun mpdel-core-selected-entities ()
"Return the selected entities in the current buffer.

If any entity is marked, return the list of all marked entities.
If no entity is marked but there is an entity at point, return a
list with this entity. Otherwise, return nil."
(navigel-marked-entities t))

(defun mpdel-core-add-to-current-playlist ()
"Add selected entities to current playlist."
(interactive)
(libmpdel-current-playlist-add (mpdel-core-selected-entities))
(mpdel-core-after-playlist-modification))
(libmpdel-current-playlist-add (mpdel-core-selected-entities)))

(defun mpdel-core-add-to-stored-playlist ()
"Add selected entities to a stored playlist."
(interactive)
(libmpdel-stored-playlist-add (mpdel-core-selected-entities))
(mpdel-core-after-playlist-modification))
(libmpdel-stored-playlist-add (mpdel-core-selected-entities)))

(defun mpdel-core-replace-current-playlist ()
"Replace current playlist with selected entities."
(interactive)
(libmpdel-current-playlist-replace (mpdel-core-selected-entities))
(mpdel-core-after-playlist-modification))
(libmpdel-current-playlist-replace (mpdel-core-selected-entities)))

(defun mpdel-core-replace-stored-playlist ()
"Replace a stored playlist with selected entities."
(interactive)
(libmpdel-stored-playlist-replace (mpdel-core-selected-entities))
(mpdel-core-after-playlist-modification))
(libmpdel-stored-playlist-replace (mpdel-core-selected-entities)))

(defun mpdel-core-insert-current-playlist ()
"Insert selected entities after currently-played song.
@@ -127,32 +92,42 @@ If no entity is selected, restart playing the current song."
(let ((entities (mpdel-core-selected-entities)))
(if (not entities)
(libmpdel-playback-seek "0")
(libmpdel-current-playlist-insert entities)
(mpdel-core-after-playlist-modification))))
(libmpdel-current-playlist-insert entities))))

(defun mpdel-core-dired (&optional pos)
"Open dired on the entity at POS, point if nil."
(defun mpdel-core-dired ()
"Open dired on the entity at point."
(interactive)
(libmpdel-dired (mpdel-core-entity-at-point pos)))

(defun mpdel-core-open-entity-at-point (&optional pos)
"Open buffer displaying information about entity at POS.
Use point if POS is nil."
(interactive)
(mpdel-core--open-entity (mpdel-core-entity-at-point (or pos (point)))))

(defun mpdel-core-open-entity-parent-at-point (&optional pos)
"Open a navigator showing the parent of entity at POS.
If POS is nil, use point.
(libmpdel-dired (navigel-entity-at-point)))

For example, if point is on a song, open a navigator on its
album."
;;;###autoload
(defun mpdel-core-open-artists ()
"Display all artists in the MPD database."
(interactive)
(let ((entity (mpdel-core-entity-at-point pos)))
(mpdel-core--open-entity (libmpdel-entity-parent entity) entity)))
(mpdel-core-open 'artists))

;;;###autoload
(defun mpdel-core-search-by-artist (name)
"Display all songs whose artist's name match NAME.
Interactively, ask for NAME."
(interactive (list (read-from-minibuffer "Search for artist: ")))
(mpdel-core-open (libmpdel-search-criteria-create :type "artist" :what name)))

;;;###autoload
(defun mpdel-core-search-by-album (name)
"Display all songs whose album's name match NAME.
Interactively, ask for NAME."
(interactive (list (read-from-minibuffer "Search for album: ")))
(mpdel-core-open (libmpdel-search-criteria-create :type "album" :what name)))

;;;###autoload
(defun mpdel-core-search-by-title (title)
"Display all songs matching TITLE.
Interactively, ask for TITLE."
(interactive (list (read-from-minibuffer "Search for title: ")))
(mpdel-core-open (libmpdel-search-criteria-create :type "title" :what title)))

;;; Define the mpdel shared map
;;; Mode

(defvar mpdel-core-map
(let ((map (make-sparse-keymap)))
@@ -165,10 +140,13 @@ album."
(define-key map (kbd "R") #'mpdel-core-replace-stored-playlist)
(define-key map (kbd "p") #'mpdel-core-insert-current-playlist)
(define-key map (kbd "C-x C-j") #'mpdel-core-dired)
(define-key map (kbd "RET") #'mpdel-core-open-entity-at-point)
(define-key map (kbd "^") #'mpdel-core-open-entity-parent-at-point)
(define-key map (kbd "n") #'mpdel-core-open-artists)
(define-key map (kbd "s s") #'mpdel-core-search-by-title)
(define-key map (kbd "s l") #'mpdel-core-search-by-album)
(define-key map (kbd "s r") #'mpdel-core-search-by-artist)
(define-key map (kbd "^") #'navigel-open-parent)
map)
"Keymap for all mpdel buffers.")
"Keybindings for all MPDel buffers.")

;; Make it possible to activate `mpdel-core-map' from a keybinding:
(fset 'mpdel-core-map mpdel-core-map)


+ 0
- 213
mpdel-nav.el View File

@@ -1,213 +0,0 @@
;;; mpdel-nav.el --- Navigate your MPD database -*- lexical-binding: t; -*-

;; Copyright (C) 2018-2019 Damien Cassou

;; Author: Damien Cassou <damien@cassou.me>
;; Keywords: multimedia
;; Url: https://gitlab.petton.fr/mpdel/mpdel
;; Package-requires: ((emacs "25.1"))
;; Version: 1.0.0

;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.

;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.

;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <https://www.gnu.org/licenses/>.

;;; Commentary:

;; User interface to browse the MPD database and add songs to
;; playlists.

;;; Code:
(require 'libmpdel)

(require 'mpdel-core)

;;; Helpers

;; The default entity is a dumb one representing all artists.
(defvar-local mpdel-nav--entity 'artists
"Stores entity (e.g., album) associated to current buffer.")

(defun mpdel-nav--buffer ()
"Return buffer for mpdel-nav."
(get-buffer-create "*MPDEL Navigator*"))

(cl-defgeneric mpdel-nav--entity-to-list-entry (entity)
"Convert ENTITY to a format suitable for the tabulated list."
(list entity
(vector (libmpdel-entity-name entity))))

(defun mpdel-nav--default-tabulated-list-format ()
"Return `tabulated-list-format' value for any non-specific entity."
(vector (list "Name" 0 t)))

(cl-defmethod mpdel-nav--entity-to-list-entry ((entity libmpdel-album))
(list entity
(vector (libmpdel-entity-name entity)
(libmpdel-artist-name entity))))

(defun mpdel-nav--album-tabulated-list-format ()
"Return `tabulated-list-format' value for albums."
(vector (list "Name" 40 t)
(list "Artist" 0 t)))

(cl-defmethod mpdel-nav--entity-to-list-entry ((entity libmpdel-song))
(list entity
(vector (libmpdel-entity-name entity)
(or (libmpdel-album-name entity) "")
(or (libmpdel-song-disc entity) "")
(or (libmpdel-entity-date entity) "")
(or (libmpdel-artist-name entity) ""))))

(defun mpdel-nav--song-tabulated-list-format ()
"Return `tabulated-list-format' value for songs."
(vector (list "Name" 30 t)
(list "Album" 30 t)
(list "Disk" 4 t)
(list "Date" 5 t)
(list "Artist" 0 t)))

(cl-defgeneric mpdel-nav--tabulated-list-format (entity)
"Return `tabulated-list-format' value for children of ENTITY.")

(cl-defmethod mpdel-nav--tabulated-list-format ((_entity (eql artists)))
(mpdel-nav--default-tabulated-list-format))

(cl-defmethod mpdel-nav--tabulated-list-format ((_entity (eql albums)))
(mpdel-nav--album-tabulated-list-format))

(cl-defmethod mpdel-nav--tabulated-list-format ((_entity (eql stored-playlists)))
(mpdel-nav--default-tabulated-list-format))

(cl-defmethod mpdel-nav--tabulated-list-format ((_entity libmpdel-stored-playlist))
(mpdel-nav--song-tabulated-list-format))

(cl-defmethod mpdel-nav--tabulated-list-format ((_entity libmpdel-artist))
(mpdel-nav--album-tabulated-list-format))

(cl-defmethod mpdel-nav--tabulated-list-format ((_entity libmpdel-album))
(mpdel-nav--song-tabulated-list-format))

(cl-defmethod mpdel-nav--tabulated-list-format ((_entity libmpdel-search-criteria))
(mpdel-nav--song-tabulated-list-format))

(defun mpdel-nav--display-wait ()
"Display a waiting message in current tabulated buffer."
(setq tabulated-list-entries
(list (list
nil
(vconcat
'("Please wait…")
(make-vector (1- (length tabulated-list-format)) "")))))
(tabulated-list-print))

(defun mpdel-nav--open (entity &optional target)
"Open a navigator buffer displaying children of ENTITY.

If TARGET is non-nil and is in buffer, move point to it."
(with-current-buffer (mpdel-nav--buffer)
(mpdel-nav-mode)
(setq mpdel-nav--entity entity)
(setq tabulated-list-format (mpdel-nav--tabulated-list-format entity))
(tabulated-list-init-header)
(mpdel-nav--display-wait)
(mpdel-nav-refresh target)
(switch-to-buffer (current-buffer))))

;;; Public functions

(defun mpdel-nav-refresh (&optional target)
"Refresh buffer.

If TARGET is non-nil and is in buffer, move point to it."
(interactive)
(libmpdel-list
mpdel-nav--entity
(lambda (entities)
(with-current-buffer (mpdel-nav--buffer)
(setq tabulated-list-entries (mapcar #'mpdel-nav--entity-to-list-entry entities))
(tabulated-list-print)
(when target
(mpdel-core-go-to-entity target))))))

(defun mpdel-nav-open-entity-parent-at-point (&optional entity)
"Refresh navigator to display parent of ENTITY among its siblings.
Use entity at point if ENTITY is nil."
(interactive)
(let* ((entity (or entity (mpdel-core-entity-at-point)))
(parent (libmpdel-entity-parent entity))
(ancestor (and parent (libmpdel-entity-parent parent))))
(if (and ancestor (libmpdel-equal parent mpdel-nav--entity))
(mpdel-nav--open ancestor parent)
(when parent
(mpdel-nav--open parent entity)))))

;;;###autoload
(defun mpdel-nav-open-artists ()
"Display all artists in the MPD database."
(interactive)
(mpdel-nav--open 'artists))

;;;###autoload
(defun mpdel-nav-open-albums ()
"Display all albums in the MPD database."
(interactive)
(mpdel-nav--open 'albums))

;;;###autoload
(defun mpdel-nav-open-stored-playlists ()
"Display all stored playlists in the MPD database."
(interactive)
(mpdel-nav--open 'stored-playlists))

;;;###autoload
(defun mpdel-nav-search-by-artist (name)
"Display all songs whose artist's name match NAME.
Interactively, ask for NAME."
(interactive (list (read-from-minibuffer "Search with artist: ")))
(mpdel-nav--open (libmpdel-search-criteria-create :type "artist" :what name)))

;;;###autoload
(defun mpdel-nav-search-by-album (name)
"Display all songs whose album's name match NAME.
Interactively, ask for NAME."
(interactive (list (read-from-minibuffer "Search with album: ")))
(mpdel-nav--open (libmpdel-search-criteria-create :type "album" :what name)))

;;;###autoload
(defun mpdel-nav-search-by-title (title)
"Display all songs matching TITLE.
Interactively, ask for TITLE."
(interactive (list (read-from-minibuffer "Search with title: ")))
(mpdel-nav--open (libmpdel-search-criteria-create :type "title" :what title)))

(defvar mpdel-nav-mode-map
(let ((map (make-sparse-keymap)))
;; inherit from both `mpdel-core-map' and
;; `tabulated-list-mode-map':
(set-keymap-parent
map
(make-composed-keymap mpdel-core-map tabulated-list-mode-map))
(define-key map (kbd "g") #'mpdel-nav-refresh)
(define-key map (kbd "^") #'mpdel-nav-open-entity-parent-at-point)
map)
"Keybindings for `mpdel-nav-mode'.")

(define-derived-mode mpdel-nav-mode tabulated-list-mode "MPD Navigator"
"Abstract major mode to list part of the MPD database."
(hl-line-mode))

(provide 'mpdel-nav)
;;; mpdel-nav.el ends here

+ 72
- 192
mpdel-playlist.el View File

@@ -27,11 +27,8 @@
;; playlists.

;;; Code:
(require 'cl-lib)
(require 'subr-x)

(require 'libmpdel)
(require 'mpdel-core)
(require 'mpdel-tablist)

;;; Customization
@@ -45,150 +42,68 @@
"Face to highlight current song in playlist."
:group 'mpdel-playlist)

(defface mpdel-playlist-name-face
'((t . (:inherit default)))
"Face for song names in playlist."
:group 'mpdel-playlist)

(defface mpdel-playlist-track-face
'((t . (:inherit default)))
"Face for track numbers in playlist."
:group 'mpdel-playlist)

(defface mpdel-playlist-album-face
'((t . (:inherit default)))
"Face for album names in playlist."
:group 'mpdel-playlist)
;;; `navigel' major-mode configuration

(defface mpdel-playlist-disk-face
'((t . (:inherit default)))
"Face for disk numbers in playlist."
:group 'mpdel-playlist)
(cl-defmethod navigel-entity-tablist-mode ((_entity (eql current-playlist)) &context (navigel-app mpdel))
(mpdel-playlist-current-playlist-mode))

(defface mpdel-playlist-date-face
'((t . (:inherit default)))
"Face for dates in playlist."
:group 'mpdel-playlist)
(cl-defmethod navigel-entity-tablist-mode ((_entity libmpdel-stored-playlist) &context (navigel-app mpdel))
(mpdel-playlist-stored-playlist-mode))

(defface mpdel-playlist-artist-face
'((t . (:inherit default)))
"Face for artist names in playlist."
:group 'mpdel-playlist)
(cl-defmethod navigel-delete ((songs list) &context (navigel-app mpdel) (major-mode mpdel-playlist-mode) &optional _callback)
(libmpdel-playlist-delete songs navigel-entity))

;;; Private variables
(defvar-local mpdel-playlist-playlist nil
"Playlist displayed in the buffer.")
;;; Private functions

;;; Helper functions

(cl-defgeneric mpdel-playlist--buffer (playlist)
"Return buffer displaying PLAYLIST.")

(cl-defmethod mpdel-playlist--buffer ((_ (eql current-playlist)))
(get-buffer-create "*MPDEL Current Playlist*"))

(cl-defmethod mpdel-playlist--buffer ((stored-playlist libmpdel-stored-playlist))
(get-buffer-create (format "*MPDEL Playlist: %s*"
(libmpdel-entity-name stored-playlist))))

(defun mpdel-playlist--song-to-list-entry (song)
"Convert SONG to a format suitable for the tabulated list."
(list song
(vector
(propertize (or (libmpdel-entity-name song) "") 'face 'mpdel-playlist-name-face)
(propertize (or (libmpdel-song-track song) "") 'face 'mpdel-playlist-track-face)
(propertize (or (libmpdel-album-name song) "") 'face 'mpdel-playlist-album-face)
(propertize (or (libmpdel-song-disc song) "") 'face 'mpdel-playlist-disk-face)
(propertize (or (libmpdel-entity-date song) "") 'face 'mpdel-playlist-date-face)
(propertize (or (libmpdel-artist-name song) "") 'face 'mpdel-playlist-artist-face))))

(defun mpdel-playlist-go-to-song (&optional song)
"Move point to SONG, currently-played song if nil.
Return non-nil if SONG is found, nil otherwise."
(mpdel-core-go-to-entity (or song (libmpdel-current-song))))

(defun mpdel-playlist-highlight-song (&optional song)
"Highlight SONG, current song if nil."
(defun mpdel-playlist--highlight ()
"Highlight currently played song in current buffer."
(save-excursion
(when (mpdel-playlist-go-to-song song)
(when (navigel-go-to-entity (libmpdel-current-song))
(let ((inhibit-read-only t))
(put-text-property (line-beginning-position) (line-end-position)
'face 'mpdel-playlist-current-song-face)))))

(defun mpdel-playlist--save-playlist-status ()
"Return an object representing selection.
Restore selection with `mpdel-playlist--restore-playlist-status'."
(cons
(mpdel-core-entity-at-point (point))
(mpdel-core-entity-at-point (mark t))))

(defun mpdel-playlist--restore-playlist-status (status)
"Restore playlist selection STATUS.
STATUS has been returned by `mpdel-playlist--save-playlist-status'."
(when (cdr status)
(mpdel-playlist-go-to-song (cdr status))
(push-mark nil t))
(when (car status)
(mpdel-playlist-go-to-song (car status))))

(defun mpdel-playlist--imenu-prev-index-position ()
"Move point to previous line in playlist buffer.
This function is used as a value for
`imenu-prev-index-position-function'."
(unless (bobp)
(forward-line -1)))

(defun mpdel-playlist--imenu-extract-index-name ()
"Return imenu name for line at point.
This function is used as a value for
`imenu-extract-index-name-function'. Point should be at the
beginning of the line."
(let ((song (mpdel-core-entity-at-point)))
(format "%s/%s/%s"
(or (libmpdel-artist-name song) "??")
(or (libmpdel-album-name song) "??")
(libmpdel-entity-name song))))
;;; Private functions

(defun mpdel-playlist--register-to-hooks ()
"Register to several hooks to refresh automatically refresh the current buffer.."
(let ((buffer (current-buffer)))
(let* ((refresh-fn (lambda ()
(with-current-buffer buffer
(navigel-refresh))))
(playlist navigel-entity)
(hooks (if (libmpdel-stored-playlist-p playlist)
'(libmpdel-stored-playlist-changed-hook)
'(libmpdel-current-playlist-changed-hook
libmpdel-current-song-changed-hook
libmpdel-player-changed-hook))))
(dolist (hook hooks)
(add-hook hook refresh-fn))
(add-hook 'kill-buffer-hook
(lambda () (dolist (hook hooks) (remove-hook hook refresh-fn)))
nil t))))

;;; Commands
;;; Public functions

(defun mpdel-playlist-refresh (&optional buffer)
"Clear and re-populate the playlist BUFFER.
Use current buffer if BUFFER is nil."
(interactive)
(let ((buffer (or buffer (current-buffer))))
(when (buffer-live-p buffer)
(with-current-buffer buffer
(libmpdel-list
mpdel-playlist-playlist
(lambda (songs)
(let ((playlist-status (mpdel-playlist--save-playlist-status)))
(setq tabulated-list-entries (mapcar #'mpdel-playlist--song-to-list-entry songs))
(tabulated-list-print)
(mpdel-playlist--restore-playlist-status playlist-status)
(when (and (not (libmpdel-stopped-p)) (libmpdel-current-playlist-p mpdel-playlist-playlist))
(mpdel-playlist-highlight-song)))))))))

(defun mpdel-playlist-delete ()
"Delete selected songs from current playlist."
;;;###autoload
(defun mpdel-playlist-open ()
"Display the current playlist."
(interactive)
(let ((songs (mpdel-core-selected-entities)))
(when songs
(libmpdel-playlist-delete songs mpdel-playlist-playlist)
;; Move point to the closest non-deleted song
(forward-line 1)
(when (= (point) (point-max))
(forward-line -2))
(setq deactivate-mark t))))

(defun mpdel-playlist-play ()
"Start playing the song at point."
(mpdel-core-open 'current-playlist))

(define-key mpdel-core-map (kbd "l") #'mpdel-playlist-open)

;;;###autoload
(defun mpdel-playlist-open-stored-playlist ()
"Ask for a stored playlist and open it."
(interactive)
(if (libmpdel-current-playlist-p mpdel-playlist-playlist)
(libmpdel-play-song (mpdel-core-entity-at-point))
(mpdel-core-insert-current-playlist)))
(libmpdel-funcall-on-stored-playlist #'mpdel-core-open))

(define-key mpdel-core-map (kbd "L") #'mpdel-playlist-open-stored-playlist)

(defun mpdel-playlist-move-up ()
"Move selected songs up in the current playlist."
@@ -208,76 +123,41 @@ Use current buffer if BUFFER is nil."
"Save current playlist into a new stored playlist.
Ask for stored playlist name."
(interactive)
(if (libmpdel-current-playlist-p mpdel-playlist-playlist)
(if (libmpdel-current-playlist-p navigel-entity)
(call-interactively #'libmpdel-playlist-save)
(user-error "You can only save from the current playlist")))

(defun mpdel-playlist--register-to-hooks (buffer)
"Register to several hooks to refresh BUFFER."
(with-current-buffer buffer
(let* ((refresh-fn (lambda () (mpdel-playlist-refresh buffer)))
(playlist mpdel-playlist-playlist)
(hooks (if (libmpdel-stored-playlist-p playlist)
'(libmpdel-stored-playlist-changed-hook)
'(libmpdel-current-playlist-changed-hook
libmpdel-current-song-changed-hook
libmpdel-player-changed-hook))))
(mapc (lambda (hook) (add-hook hook refresh-fn)) hooks)
(add-hook 'kill-buffer-hook
(lambda ()
(mapc (lambda (hook) (remove-hook hook refresh-fn)) hooks))
nil t))))

;;;###autoload
(defun mpdel-playlist-open (&optional playlist)
"Open a buffer with PLAYLIST, current playlist if nil."
(interactive)
(let* ((playlist (or playlist 'current-playlist))
(buffer (mpdel-playlist--buffer playlist)))
(with-current-buffer buffer
(mpdel-playlist-mode)
(setq mpdel-playlist-playlist playlist)
(mpdel-playlist-refresh buffer))
(switch-to-buffer buffer)
(mpdel-playlist--register-to-hooks buffer)))

;;;###autoload
(defun mpdel-playlist-open-stored-playlist ()
"Ask for a stored playlist and open it."
(interactive)
(libmpdel-funcall-on-stored-playlist #'mpdel-playlist-open))

;;; Major mode
;;; Modes

(defvar mpdel-playlist-mode-map
(let ((map (make-sparse-keymap)))
;; inherit from both `mpdel-core-map' and
;; `tabulated-list-mode-map':
(set-keymap-parent
map
(make-composed-keymap mpdel-core-map tabulated-list-mode-map))
(define-key map (kbd "g") #'mpdel-playlist-refresh)
(define-key map (kbd "k") #'mpdel-playlist-delete)
(define-key map (kbd "p") #'mpdel-playlist-play)
map)
"Keybindings for `mpdel-playlist-mode'.")

(define-derived-mode mpdel-playlist-mode mpdel-tablist-mode "MPDel Playlist"
(add-hook 'navigel-init-done-hook #'mpdel-playlist--register-to-hooks nil t))

(defvar mpdel-playlist-current-playlist-mode-map
(let ((map (make-sparse-keymap)))
(define-key map (kbd "<M-up>") #'mpdel-playlist-move-up)
(define-key map (kbd "<M-down>") #'mpdel-playlist-move-down)
(define-key map (kbd "C-x C-s") #'mpdel-playlist-save)
map))
(define-derived-mode mpdel-playlist-mode tabulated-list-mode "Playlist"
"Display and manipulate the current MPD playlist."
(setq tabulated-list-format
(vector (list "Title" 30 nil)
(list "#" 6 nil)
(list "Album" 30 nil)
(list "Disk" 4 nil)
(list "Date" 5 nil)
(list "Artist" 0 nil)))
(tabulated-list-init-header)
(setq imenu-prev-index-position-function #'mpdel-playlist--imenu-prev-index-position)
(setq imenu-extract-index-name-function #'mpdel-playlist--imenu-extract-index-name)
(hl-line-mode))
map)
"Keybindings for `mpdel-playlist-current-playlist-mode'.")

(define-derived-mode mpdel-playlist-current-playlist-mode mpdel-playlist-mode "MPDel Current playlist"
"Major mode to display the current playlist."
(add-hook 'navigel-changed-hook #'mpdel-playlist--highlight nil t))

(defvar mpdel-playlist-stored-playlist-mode-map
(let ((map (make-sparse-keymap)))
map)
"Keybindings for `mpdel-playlist-stored-playlist-mode'.")


(define-derived-mode mpdel-playlist-stored-playlist-mode mpdel-playlist-mode "MPDel Stored playlist"
"Major mode to display a stored playlist.")

(provide 'mpdel-playlist)
;;; mpdel-playlist.el ends here

+ 62
- 8
mpdel-song.el View File

@@ -80,16 +80,20 @@ When non-nil, the buffer keeps showing the current song, even
when the song changes.")

;;; `navigel' configuration

(cl-defmethod navigel-open ((song libmpdel-song) _target &context (navigel-app mpdel))
(mpdel-song-open song))

(cl-defmethod navigel-entity-at-point (&context (navigel-app mpdel) (major-mode mpdel-song-mode))
mpdel-song-song)

;;; Helper functions

(defvar mpdel-song--timer nil
"Store timer to refresh the seek buffer.")

(defun mpdel-song-buffer-song (&optional buffer)
"Return song displayed in BUFFER, current one if nil."
(with-current-buffer (or buffer (current-buffer))
mpdel-song-song))

(defun mpdel-song--start-timer ()
"Start refresh timer."
(unless mpdel-song--timer
@@ -142,6 +146,7 @@ In particular, it must contain key symbol `elapsed' and symbol
(with-current-buffer buffer
(let ((inhibit-read-only t))
(erase-buffer)
(setq mpdel-song-song (libmpdel-current-song))
(mpdel-song--display-play-state)
(mpdel-song--display-metadata)
(mpdel-song--display-play-time data))))
@@ -187,9 +192,11 @@ playback."
(when current-song-p
(add-hook 'libmpdel-player-changed-hook refresh-fn)
(add-hook 'kill-buffer-hook #'mpdel-song--stop-timer nil t)
(add-hook 'kill-buffer-hook (lambda () (remove-hook 'libmpdel-player-changed-hook refresh-fn))))
(add-hook 'kill-buffer-hook (lambda () (remove-hook 'libmpdel-player-changed-hook refresh-fn)) nil t))
(pop-to-buffer (current-buffer)))))

(define-key mpdel-core-map (kbd "v") #'mpdel-song-open)

(defun mpdel-song-play ()
"Start playing the song of the current buffer."
(interactive)
@@ -204,6 +211,51 @@ refreshing itself to display playback position."
(interactive)
(quit-window t))

(defun mpdel-song-small-increment ()
"Move forward by value of variable `mpdel-song-small-increment'."
(interactive)
(mpdel-song--seek mpdel-song-small-increment))

(define-key mpdel-core-map (kbd "F") #'mpdel-song-small-increment)

(defun mpdel-song-normal-increment ()
"Move forward by value of variable `mpdel-song-normal-increment'."
(interactive)
(mpdel-song--seek mpdel-song-normal-increment))

(define-key mpdel-core-map (kbd "f") #'mpdel-song-normal-increment)

(defun mpdel-song-large-increment ()
"Move forward by value of variable `mpdel-song-large-increment'."
(interactive)
(mpdel-song--seek mpdel-song-large-increment))

(define-key mpdel-core-map (kbd "M-f") #'mpdel-song-large-increment)

(defun mpdel-song-small-decrement ()
"Move backward by value of variable `mpdel-song-small-decrement'."
(interactive)
(mpdel-song--seek mpdel-song-small-decrement))

(define-key mpdel-core-map (kbd "B") #'mpdel-song-small-decrement)

(defun mpdel-song-normal-decrement ()
"Move backward by value of variable `mpdel-song-normal-decrement'."
(interactive)
(mpdel-song--seek mpdel-song-normal-decrement))

(define-key mpdel-core-map (kbd "b") #'mpdel-song-normal-decrement)

(defun mpdel-song-large-decrement ()
"Move backward by value of variable `mpdel-song-large-decrement'."
(interactive)
(mpdel-song--seek mpdel-song-large-decrement))

(define-key mpdel-core-map (kbd "M-b") #'mpdel-song-large-decrement)

;;; Major-mode

(defvar mpdel-song-mode-map
(let ((map (make-sparse-keymap)))
(set-keymap-parent
@@ -212,10 +264,12 @@ refreshing itself to display playback position."
(define-key map (kbd "g") #'mpdel-song-refresh)
(define-key map (kbd "p") #'mpdel-song-play)
(define-key map (kbd "q") #'mpdel-song-quit-window)
map))
map)
"Keybindgs for `mpdel-song-mode'.")

(define-derived-mode mpdel-song-mode special-mode "MPDEL song"
"Guide the user to seek inside current song.")
"Guide the user to seek inside current song."
(setq-local navigel-app 'mpdel))

(provide 'mpdel-song)
;;; mpdel-song.el ends here

+ 144
- 0
mpdel-tablist.el View File

@@ -0,0 +1,144 @@
;;; mpdel-tablist.el --- Support representing MPD entities in tablists -*- lexical-binding: t; -*-

;; Copyright (C) 2018-2019 Damien Cassou

;; Author: Damien Cassou <damien@cassou.me>
;; Keywords: multimedia
;; Url: https://gitlab.petton.fr/mpdel/mpdel
;; Package-requires: ((emacs "25.1"))
;; Version: 1.0.0

;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.

;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.

;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <https://www.gnu.org/licenses/>.

;;; Commentary:

;; This file provides some common ground for all MPDel
;; user-interfaces.

;;; Code:
(require 'libmpdel)
(require 'navigel)

(require 'mpdel-core)

;;; Customization

(defgroup mpdel-tablist nil
"Display and manipulate MPD through tablists."
:group 'libmpdel)

(defface mpdel-tablist-song-name-face
'((t . (:inherit default)))
"Face for song names in playlist.")

(defface mpdel-tablist-track-face
'((t . (:inherit default)))
"Face for track numbers in playlist.")

(defface mpdel-tablist-album-face
'((t . (:inherit default)))
"Face for album names in playlist.")

(defface mpdel-tablist-disk-face
'((t . (:inherit default)))
"Face for disk numbers in playlist.")

(defface mpdel-tablist-date-face
'((t . (:inherit default)))
"Face for dates in playlist.")

(defface mpdel-tablist-artist-face
'((t . (:inherit default)))
"Face for artist names in playlist.")

;;; `navigel' major-mode configuration

(cl-defmethod navigel-entity-tablist-mode ((_entity (eql artists)) &context (navigel-app mpdel))
(mpdel-tablist-mode))

(cl-defmethod navigel-entity-tablist-mode ((_entity libmpdel-search-criteria) &context (navigel-app mpdel))
(mpdel-tablist-mode))

(cl-defmethod navigel-entity-tablist-mode ((_entity libmpdel-album) &context (navigel-app mpdel))
(mpdel-tablist-mode))

(cl-defmethod navigel-entity-tablist-mode ((_entity libmpdel-album) &context (navigel-app mpdel))
(mpdel-tablist-mode))


;;; `navigel' tabulated list configuration

(defun mpdel-tablist--album-format ()
"Return `tabulated-list-format' value for albums."
(vector (list "Name" 40 t)
(list "Artist" 0 t)))

(defun mpdel-tablist--song-format ()
"Return `tabulated-list-format' value for songs."
(vector (list "Title" 30 t)
(list "#" 6 nil)
(list "Album" 30 t)
(list "Disk" 4 t)
(list "Date" 5 t)
(list "Artist" 0 t)))

(cl-defmethod navigel-entity-to-columns ((entity libmpdel-album) &context (navigel-app mpdel))
(vector (libmpdel-entity-name entity)
(libmpdel-artist-name entity)))

(cl-defmethod navigel-entity-to-columns ((song libmpdel-song))
(vector
(propertize (or (libmpdel-entity-name song) "") 'face 'mpdel-tablist-song-name-face)
(propertize (or (libmpdel-song-track song) "") 'face 'mpdel-tablist-track-face)
(propertize (or (libmpdel-album-name song) "") 'face 'mpdel-tablist-album-face)
(propertize (or (libmpdel-song-disc song) "") 'face 'mpdel-tablist-disk-face)
(propertize (or (libmpdel-entity-date song) "") 'face 'mpdel-tablist-date-face)
(propertize (or (libmpdel-artist-name song) "") 'face 'mpdel-tablist-artist-face)))

(cl-defmethod navigel-tablist-format ((_entity libmpdel-artist) &context (navigel-app mpdel))
(mpdel-tablist--album-format))

(cl-defmethod navigel-tablist-format ((_entity libmpdel-album) &context (navigel-app mpdel))
(mpdel-tablist--song-format))

(cl-defmethod navigel-tablist-format ((_entity libmpdel-search-criteria) &context (navigel-app mpdel))
(mpdel-tablist--song-format))

(cl-defmethod navigel-tablist-format ((_entity (eql current-playlist)) &context (navigel-app mpdel))
(mpdel-tablist--song-format))

(cl-defmethod navigel-tablist-format ((_entity libmpdel-stored-playlist) &context (navigel-app mpdel))
(mpdel-tablist--song-format))

;;; Major-mode

(defvar mpdel-tablist-mode-map
(let ((map (make-sparse-keymap)))
;; inherit from both `mpdel-core-map' and
;; `navigel-tablist-mode-map':
(set-keymap-parent
map
(make-composed-keymap mpdel-core-map navigel-tablist-mode-map))
(define-key map (kbd "k") #'tablist-do-delete)
map)
"Keybindings for `mpdel-core-tablist-mode'.")

(define-derived-mode mpdel-tablist-mode navigel-tablist-mode "MPDel Tablist")

(provide 'mpdel-tablist)
;;; mpdel-tablist.el ends here

+ 6
- 77
mpdel.el View File

@@ -5,7 +5,7 @@
;; Author: Damien Cassou <damien@cassou.me>
;; Keywords: multimedia
;; Url: https://gitlab.petton.fr/mpdel/mpdel
;; Package-requires: ((emacs "25.1") (libmpdel "1.0.0"))
;; Package-requires: ((emacs "25.1") (libmpdel "1.0.0") (navigel "0.4.0"))
;; Version: 1.0.0

;; This program is free software; you can redistribute it and/or modify
@@ -31,16 +31,14 @@

;;; Code:

(require 'libmpdel)
(require 'mpdel-core)
(require 'mpdel-playlist)
(require 'mpdel-song)
(require 'mpdel-nav)
(require 'mpdel-playlist)
(require 'mpdel-tablist)

;;; Customization
(defgroup mpdel nil
"Configure mpdel's global minor mode."
"Configure MPDel."
:group 'libmpdel)

(defcustom mpdel-prefix-key (kbd "C-x Z")
@@ -48,78 +46,9 @@
:type 'key-sequence)

;;; Add features to all mpdel buffers
(defun mpdel-song-small-increment ()
"Move forward by value of variable `mpdel-song-small-increment'."
(interactive)
(mpdel-song--seek mpdel-song-small-increment))

(define-key mpdel-core-map (kbd "F") #'mpdel-song-small-increment)

(defun mpdel-song-normal-increment ()
"Move forward by value of variable `mpdel-song-normal-increment'."
(interactive)
(mpdel-song--seek mpdel-song-normal-increment))

(define-key mpdel-core-map (kbd "f") #'mpdel-song-normal-increment)

(defun mpdel-song-large-increment ()
"Move forward by value of variable `mpdel-song-large-increment'."
(interactive)
(mpdel-song--seek mpdel-song-large-increment))

(define-key mpdel-core-map (kbd "M-f") #'mpdel-song-large-increment)

(defun mpdel-song-small-decrement ()
"Move backward by value of variable `mpdel-song-small-decrement'."
(interactive)
(mpdel-song--seek mpdel-song-small-decrement))

(define-key mpdel-core-map (kbd "B") #'mpdel-song-small-decrement)

(defun mpdel-song-normal-decrement ()
"Move backward by value of variable `mpdel-song-normal-decrement'."
(interactive)
(mpdel-song--seek mpdel-song-normal-decrement))

(define-key mpdel-core-map (kbd "b") #'mpdel-song-normal-decrement)

(defun mpdel-song-large-decrement ()
"Move backward by value of variable `mpdel-song-large-decrement'."
(interactive)
(mpdel-song--seek mpdel-song-large-decrement))

(define-key mpdel-core-map (kbd "M-b") #'mpdel-song-large-decrement)

(cl-defmethod mpdel-core--open-entity ((entity t) &optional target)
;; By default, open any entity with a navigator
(mpdel-nav--open entity target))

(cl-defmethod mpdel-core--open-entity ((song libmpdel-song) &optional _target)
(mpdel-song-open song))

(define-key mpdel-core-map (kbd "l") #'mpdel-playlist-open)
(define-key mpdel-core-map (kbd "L") #'mpdel-playlist-open-stored-playlist)
(define-key mpdel-core-map (kbd "n") #'mpdel-nav-open-artists)
(define-key mpdel-core-map (kbd "N") #'mpdel-nav-open-stored-playlists)
(define-key mpdel-core-map (kbd "v") #'mpdel-song-open)
(define-key mpdel-core-map (kbd "s s") #'mpdel-nav-search-by-title)
(define-key mpdel-core-map (kbd "s l") #'mpdel-nav-search-by-album)
(define-key mpdel-core-map (kbd "s r") #'mpdel-nav-search-by-artist)

;;; Add features to the song buffers
(cl-defmethod mpdel-core--entity-at-point (_pos (_mode (derived-mode mpdel-song-mode)))
(mpdel-song-buffer-song))
;;; Minor mode: Define the global minor mode so users can control MPD
;;; from non-MPDel buffers

(defun mpdel-song-navigate ()
"Open a navigator containing song at point."
(interactive)
(mpdel-nav--open (libmpdel-entity-parent (mpdel-song-buffer-song))))

;;; Define the global minor mode so users can control MPD from non-MPD
;;; buffers
(defvar mpdel-mode-map
(let ((map (make-sparse-keymap)))
(define-key map mpdel-prefix-key 'mpdel-core-map)


Loading…
Cancel
Save