[Top][All Lists]
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[bongo-devel] Marks
From: |
Daniel Jensen |
Subject: |
[bongo-devel] Marks |
Date: |
Wed, 10 Jan 2007 15:20:32 +0100 |
User-agent: |
Gnus/5.110006 (No Gnus v0.6) Emacs/22.0.92 (gnu/linux) |
Here's a patch, introducing marks. I'd appreciate your comments.
I emulated dired where applicable. Currently only track lines can be
marked. For some reason I did not think header lines should be marked.
Marking commands:
key binding
--- -------
m bongo-mark-line
u bongo-unmark-line
U bongo-unmark-all-marks
% m bongo-mark-lines-regexp
% a bongo-mark-lines-artist-regexp
% b bongo-mark-lines-album-regexp
% t bongo-mark-lines-track-regexp
% y bongo-mark-lines-year-regexp
Commands modified for using marks:
key binding
--- -------
e bongo-append-enqueue
E bongo-insert-enqueue
RET bongo-dwim
k bongo-kill-line
c bongo-copy-line-as-kill
Yanking killed lines is broken!
Customize the display with `bongo-mark-character' and face
`bongo-marked-line'.
diff -Naur bongo-old/bongo.el bongo-new/bongo.el
--- bongo-old/bongo.el 2007-01-08 19:56:57.000000000 +0100
+++ bongo-new/bongo.el 2007-01-10 15:10:56.000000000 +0100
@@ -3055,6 +3055,181 @@
(bongo-maybe-insert-intermediate-header)))))
+;;;; Marks
+
+(defvar bongo-marks nil
+ "A list of marked lines in the buffer.")
+(make-variable-buffer-local 'bongo-marks)
+
+(defun bongo-line-marker ()
+ "Return a new marker for the current line."
+ (set-marker (make-marker) (bongo-point-at-bol)))
+
+(defun bongo-marked-track-line-p ()
+ "Return non-nil if the line at point is marked."
+ (member (bongo-line-marker) bongo-marks))
+
+(defun bongo-force-visible-line ()
+ "Expand section headers above if the current line is invisible."
+ ;; redisplaying an invisible is broken
+ (while (bongo-before-invisible-text-p)
+ (save-excursion
+ (goto-char (bongo-point-before-previous-line-satisfying
+ (lambda ()
+ (and (bongo-header-line-p)
+ (not (bongo-before-invisible-text-p))))))
+ (bongo-expand))))
+
+(defun bongo-set-mark (&optional unset)
+ "Set a mark at line, or clear it if UNSET is non-nil."
+ (if unset
+ (setq bongo-marks (delete (bongo-line-marker) bongo-marks))
+ (add-to-list 'bongo-marks (bongo-line-marker)))
+ (bongo-force-visible-line)
+ (bongo-redisplay-line))
+
+(defun bongo-repeat-over-tracks (n function)
+ "Repeat FUNCTION over the next N track lines."
+ (while (> n 0)
+ (funcall function)
+ (let ((next (bongo-point-at-next-track-line)))
+ (when next
+ (goto-char next))
+ (setq n (if next (1- n) 0))))
+ (while (< n 0)
+ (let ((prev (bongo-point-at-previous-track-line)))
+ (when prev
+ (goto-char prev))
+ (setq n (if prev (1+ n) 0)))
+ (funcall function)))
+
+(defun bongo-mark-line (n)
+ "Mark the N following tracks for use in later Bongo commands."
+ (interactive "p")
+ (unless (bongo-track-line-p)
+ (goto-char (bongo-point-at-next-track-line)))
+ (bongo-repeat-over-tracks n 'bongo-set-mark))
+
+(defun bongo-unmark-line (n)
+ "Remove marks from the N following lines."
+ (interactive "p")
+ (unless (bongo-track-line-p)
+ (goto-char (bongo-point-at-next-track-line)))
+ (bongo-repeat-over-tracks n (lambda () (bongo-set-mark 'unset))))
+
+(defun bongo-line-matches-p (regexp readers)
+ "Return t if a field in the line at point matches REGEXP.
+Argument READERS is a list of infoset reader functions to limit
+search on their values."
+ (catch 'match
+ (let ((infoset (bongo-line-infoset)))
+ (dolist (reader readers)
+ (let ((value (funcall reader infoset)))
+ (when (and value (string-match regexp value))
+ (throw 'match t)))))))
+
+(defun bongo-mark-lines-matching (regexp infoset-readers)
+ "Mark lines matching REGEXP.
+Return the number of lines marked. See `bongo-line-matches-p'."
+ (save-excursion
+ (let ((line-move-ignore-invisible nil)
+ (mark-count 0))
+ (goto-char (point-min))
+ (while (not (eobp))
+ (when (and (bongo-track-line-p)
+ (bongo-line-matches-p regexp infoset-readers))
+ (bongo-set-mark)
+ (setq mark-count (1+ mark-count)))
+ (forward-line))
+ mark-count)))
+
+(defun bongo-repeat-over-marked-lines (function &optional mark-list)
+ "Call FUNCTION at every marked line.
+Use `bongo-marks' or optional MARK-LIST."
+ (save-excursion
+ (let ((line-move-ignore-invisible nil))
+ (dolist (mark (or mark-list bongo-marks))
+ (goto-char mark)
+ (funcall function)))))
+
+(defun bongo-unmark-all-marks ()
+ "Remove all marks from the buffer."
+ (interactive)
+ (let ((old-marks bongo-marks))
+ (setq bongo-marks nil)
+ (bongo-repeat-over-marked-lines
+ (lambda ()
+ (bongo-force-visible-line)
+ (bongo-redisplay-line))
+ old-marks)))
+
+(defun bongo-process-marks (function &optional mark-list)
+ "Call FUNCTION on marked lines in the buffer.
+Use in reverse order marks in `bongo-marks' or optional MARK-LIST."
+ (bongo-repeat-over-marked-lines function
+ (or mark-list (reverse bongo-marks)))
+ (bongo-unmark-all-marks))
+
+(defvar bongo-regexp-history nil
+ "History list of regular expressions used in Bongo commands.")
+
+(defun bongo-read-regexp (prompt)
+ (read-from-minibuffer prompt nil nil nil 'bongo-regexp-history))
+
+(defun bongo-mark-lines-regexp (regexp &optional separate-fields)
+ "Mark lines with fields matching REGEXP.
+With prefix arg, match fields artist, album and track title
+separately. Otherwise, match against a formatted infoset."
+ (interactive
+ (list (bongo-read-regexp "Mark (regexp): ")
+ current-prefix-arg))
+ (message "Marked %d matching tracks."
+ (bongo-mark-lines-matching regexp
+ (if separate-fields
+ '(bongo-infoset-artist-name
+ bongo-infoset-album-title
+ bongo-infoset-track-title)
+ '(bongo-format-infoset)))))
+
+(defun bongo-mark-lines-artist-regexp (regexp)
+ "Mark lines with artist fields matching REGEXP."
+ (interactive
+ (list (bongo-read-regexp "Mark artists (regexp): ")))
+ (message "Marked %d matching tracks."
+ (bongo-mark-lines-matching regexp '(bongo-infoset-artist-name))))
+
+(defun bongo-mark-lines-album-regexp (regexp)
+ "Mark lines with album fields matching REGEXP."
+ (interactive
+ (list (bongo-read-regexp "Mark albums (regexp): ")))
+ (message "Marked %d matching tracks."
+ (bongo-mark-lines-matching regexp '(bongo-infoset-album-title))))
+
+(defun bongo-mark-lines-track-regexp (regexp)
+ "Mark lines with track fields matching REGEXP."
+ (interactive
+ (list (bongo-read-regexp "Mark track titles (regexp): ")))
+ (message "Marked %d matching tracks."
+ (bongo-mark-lines-matching regexp '(bongo-infoset-track-title))))
+
+(defun bongo-mark-lines-year-regexp (regexp)
+ "Mark lines with year fields matching REGEXP."
+ (interactive
+ (list (bongo-read-regexp "Mark year (regexp): ")))
+ (message "Marked %d matching tracks."
+ (bongo-mark-lines-matching regexp '(bongo-infoset-album-year))))
+
+(defface bongo-marked-line
+ '((t (:inherit font-lock-warning-face)))
+ "Face used for marking Bongo track lines."
+ :group 'bongo-faces)
+
+(defcustom bongo-mark-character ?*
+ "Character prefixed to marked lines."
+ :type 'character
+ :group 'bongo-display)
+
+
;;;; Backends
(defun bongo-backend (backend-name)
@@ -4945,14 +5120,22 @@
If point is neither on a track nor on a header, do nothing.
With numerical prefix argument N, play or enqueue the next N tracks.
With \\[universal-argument] as prefix argument in a playlist buffer,
- intra-playlist-enqueue the track instead of playing it."
+ intra-playlist-enqueue the track instead of playing it.
+If there are marked tracks and the buffer is a library buffer,
+enqueue the tracks and start playing the first one."
(interactive "P")
(cond ((and (bongo-track-line-p) (bongo-library-buffer-p))
- (let ((position (if (bongo-playing-p)
- (bongo-insert-enqueue-line
- (prefix-numeric-value n))
- (bongo-append-enqueue-line
- (prefix-numeric-value n)))))
+ (let ((position nil)
+ (mode (if (bongo-playing-p) 'insert 'append)))
+ (if bongo-marks
+ (bongo-process-marks
+ (lambda ()
+ (let ((nextpos (bongo-enqueue-line mode)))
+ (setq position (or position nextpos))))
+ ;; prevent reverse insertion
+ (when (eq mode 'insert) bongo-marks))
+ (setq position
+ (bongo-enqueue-line mode (prefix-numeric-value n))))
(with-bongo-playlist-buffer
(bongo-play-line position))))
((and (bongo-track-line-p) (bongo-playlist-buffer-p))
@@ -6505,12 +6688,18 @@
(invisible (bongo-line-get-property 'invisible))
(currently-playing (bongo-currently-playing-track-line-p))
(played (bongo-played-track-line-p))
+ (marked (bongo-marked-track-line-p))
(properties (bongo-line-get-semantic-properties)))
(save-excursion
(bongo-clear-line)
(bongo-line-set-properties properties)
- (dotimes (dummy indentation)
- (insert bongo-indentation-string))
+ (let ((str ""))
+ (dotimes (dummy indentation)
+ (setq str (concat str bongo-indentation-string)))
+ (if marked
+ (insert bongo-mark-character
+ (substring str (if (string= str "") 0 1)))
+ (insert str)))
(let* ((bongo-infoset-formatting-target
(current-buffer))
(bongo-infoset-formatting-target-line
@@ -6520,7 +6709,9 @@
(when invisible
(list 'invisible invisible)))))
(insert
- (cond (header
+ (cond (marked
+ (bongo-facify content 'bongo-marked-line))
+ (header
(bongo-format-header content collapsed))
(currently-playing
(bongo-facify content 'bongo-currently-playing-track))
@@ -6652,11 +6843,19 @@
"In Bongo, kill the current section, track, or line.
If the current line is a header line, kill the whole section.
If the current line is a track line, kill the track.
+If there are marked lines, kill them instead.
Otherwise, just kill the line as `kill-line' would.
See also `bongo-copy-line-as-kill'."
(interactive)
(let ((inhibit-read-only t))
(cond
+ (bongo-marks
+ (let ((mark-list (nreverse bongo-marks)))
+ (setq bongo-marks nil)
+ (bongo-process-marks (lambda ()
+ (bongo-kill-line)
+ (append-next-kill))
+ mark-list)))
((bongo-track-line-p)
(when (bongo-current-track-line-p)
(bongo-unset-current-track-position))
@@ -6685,6 +6884,7 @@
"In Bongo, save the current object as if killed, but don't kill it.
If the current line is a header line, copy the whole section.
If the current line is a track line, copy the track.
+If there are marked lines, copy them instead.
Otherwise, just copy the current line.
If SKIP is non-nil, move point past the copied text after copying.
@@ -6692,14 +6892,23 @@
See also `bongo-kill-line'."
(interactive "p")
- (when (eq last-command 'bongo-copy-line-as-kill)
- (append-next-kill))
- (let ((end (if (bongo-object-line-p)
- (bongo-point-after-object)
- (bongo-point-after-line))))
- (copy-region-as-kill (bongo-point-before-line) end)
- (when skip
- (goto-char end))))
+ (if bongo-marks
+ (let ((mark-list (nreverse bongo-marks)))
+ (setq bongo-marks nil)
+ (bongo-process-marks (lambda ()
+ (bongo-copy-line-as-kill)
+ (append-next-kill))
+ mark-list)
+ (setq bongo-marks mark-list)
+ (bongo-unmark-all-marks))
+ (when (eq last-command 'bongo-copy-line-as-kill)
+ (append-next-kill))
+ (let ((end (if (bongo-object-line-p)
+ (bongo-point-after-object)
+ (bongo-point-after-line))))
+ (copy-region-as-kill (bongo-point-before-line) end)
+ (when skip
+ (goto-char end)))))
(defun bongo-kill-region (&optional beg end)
"In Bongo, kill the lines in the region between BEG and END.
@@ -6941,11 +7150,19 @@
(defun bongo-enqueue (mode &optional n)
"Insert the next N tracks or sections into the Bongo playlist.
If the region is active, ignore N and enqueue the region instead.
+If there are marked tracks, ignore N and enqueue them instead.
If MODE is `insert', insert just below the current track.
If MODE is `append', append to the end of the playlist."
- (if (bongo-region-active-p)
- (bongo-enqueue-region mode (region-beginning) (region-end))
- (bongo-enqueue-line mode n 'skip)))
+ (cond (bongo-marks
+ (bongo-process-marks
+ (lambda ()
+ (bongo-enqueue-line mode
+ (when (eq mode 'insert)
+ ;; prevent reverse
+ bongo-marks)))))
+ ((bongo-region-active-p)
+ (bongo-enqueue-region mode (region-beginning) (region-end)))
+ (t (bongo-enqueue-line mode n 'skip))))
(defun bongo-append-enqueue (&optional n)
"Append the next N tracks or sections to the Bongo playlist buffer.
@@ -7344,6 +7561,15 @@
(define-key map "F" 'bongo-reset-playlist)
(define-key map "r" 'bongo-rename-line)
(define-key map "d" 'bongo-dired-line)
+ (define-key map "m" 'bongo-mark-line)
+ (define-key map "u" 'bongo-unmark-line)
+ (define-key map "U" 'bongo-unmark-all-marks)
+ (define-key map "%" nil) ; For Emacs 21.
+ (define-key map "%m" 'bongo-mark-lines-regexp)
+ (define-key map "%a" 'bongo-mark-lines-artist-regexp)
+ (define-key map "%b" 'bongo-mark-lines-album-regexp)
+ (define-key map "%t" 'bongo-mark-lines-track-regexp)
+ (define-key map "%y" 'bongo-mark-lines-year-regexp)
(when (require 'volume nil t)
(if bongo-xmms-refugee-mode
(define-key map "V" 'volume)
- [bongo-devel] Marks,
Daniel Jensen <=