;;; ;;; Mode for editing CDDB files (i.e. xmcd CD database files) ;;; ;;; Created: Waider / 05/10/2000 ;;; Last Modified: Waider / 09/06/2001 ;;; ;;; Things to fix: ;;; 1. Long lines can be wrapped by putting in a second ;;; TTITLE\d+=... line, but this code won't handle that. ;;; 2. Enforce line lengths and character ranges ;;; 3. Autocomplete stuff fails badly when you're playing with non-ASCII. ;; is this strictly necessary? (defvar cddb-mode 'text-mode "CDDB mode") ;;; ;;; AutoComplete support ;;; (defun cddb-keymap-maybe-kill() "smarter version of `self-insert-command'. If the character under the cursor is the same as the key pressed, move forward over it. Otherwise, delete the specially-marked region, unset the region, and insert the key pressed." (interactive) (let ((c (aref (recent-keys) (1- (length (recent-keys)))))) (if (eq (char-after) c) (forward-char 1) (insert c) (if (and (symbolp 'rl-hilight-mark) rl-hilight-mark (not (= (point) rl-hilight-mark))) (progn (kill-region (point) rl-hilight-mark) (setq rl-hilight-mark nil)))))) (defun cddb-readline-minibuffer-hook () "Hook for autocomplete-style minibuffer hack. Stores the transient mark (rl-hilight-mark), enables `transient-mark-mode', and sets mark to the end of the buffer." (make-variable-buffer-local 'rl-hilight-mark) (setq rl-hilight-mark (+ 1 (point-max))) (make-variable-buffer-local 'transient-mark-mode) (transient-mark-mode 1) (set-mark (point-max))) (defun cddb-read-from-minibuffer( prompt &optional initial-contents keymap read hist default-value inherit-input-method) "Autocomplete version of `read-from-minibuffer' Provides similar functionality to Windows-based autocomplete typeahead, the default text is highlighted; typing the same text progressively unhighlights the text until a non-matching character is typed, at which point the rest of the line is erased." (let ((mbsh-orig minibuffer-setup-hook) new-keymap) ;; If INITIAL-CONTENTS is unset, no point in any of this trousers (if (or (null initial-contents) (if (stringp initial-contents) (string= "" initial-contents) (string= "" (car initial-contents)))) (read-from-minibuffer prompt initial-contents keymap read hist default-value inherit-input-method) ;; If the starting point on INITIAL-CONTENTS is unset, set it to 0 (or (listp initial-contents) (setq initial-contents (cons initial-contents 0))) ;; patch the keymap (if (null keymap) (setq keymap minibuffer-local-map)) ;; I'm using copy-keymap because I've already managed to chew a ;; hole in one keymap that wasn't mine... (setq new-keymap (copy-keymap keymap)) (substitute-key-definition 'self-insert-command 'cddb-keymap-maybe-kill new-keymap global-map) ;; Of course, if you've already messed up self-insert-command, ;; we're all doomed, you know. ;; unwind-protect to make sure we clean up the hook afterwards (unwind-protect (progn (add-hook 'minibuffer-setup-hook 'cddb-readline-minibuffer-hook) (read-from-minibuffer prompt initial-contents new-keymap read hist default-value inherit-input-method)) (setq minibuffer-setup-hook mbsh-orig))))) ;;; ;;; End of autocomplete stuff ;;; ;; Actual mode function (defun cddb-mode() "Mode for editing CDDB files" (interactive) (setq major-mode 'cddb-mode mode-name "CDDB") ;; make the Revision automatically bump up if the file's modified. (add-hook 'local-write-file-hooks 'cddb-bump-revision) ;; CDDB files are ISO-8859-1 (setq buffer-file-coding-system 'iso-8859-1) ;; Is this a multi-artist CD? (make-variable-buffer-local 'cddb-grip-multi-artist) (save-excursion (goto-char (point-min)) (setq cddb-grip-multi-artist (re-search-forward "^TARTIST[0-9]+=" (point-max) t))) ;; Determine if we should kick straight into edit mode ;; Three attempts to write the regexp, one of which was a typo. ;; On the downside, this isn't the most user-friendly way of ;; operating. I shouldn't use the minibuffer for all the data entry. (save-excursion (goto-char (point-min)) (if (re-search-forward "^DTITLE=\\($\\|\\s-*/\\s-*\\(Unknown\\|\\s-*$\\)\\)" (point-max) t) (cddb-edit)))) ;; Assorted editing functions (defun cddb-edit-artist() "Edit the artist/album field" (interactive) (save-excursion (goto-char (point-min)) (let ((title "") (ntitle "")) (if (re-search-forward "^DTITLE=\\(.*\\)$" (point-max) t) (setq title (buffer-substring (match-beginning 1) (match-end 1)))) (setq ntitle (cddb-read-from-minibuffer "Artist / Album: " (cons title 0) nil nil nil title nil)) (if (string= title ntitle) () (delete-region (match-beginning 1) (match-end 1)) (goto-char (match-beginning 1)) (insert ntitle))))) (defun cddb-edit-genre() "Edit the genre field" (interactive) (save-excursion (goto-char (point-min)) (let ((genre "") ngenre) (if (re-search-forward "^DGENRE=\\(.*\\)$" (point-max) t) (setq genre (buffer-substring (match-beginning 1) (match-end 1)))) (setq ngenre (cddb-read-from-minibuffer "Genre: " (cons genre 0) nil nil nil genre nil)) (if (string= genre ngenre) () (delete-region (match-beginning 1) (match-end 1)) (goto-char (match-beginning 1)) (insert ngenre))))) (defun cddb-edit-year() "Edit the year field This is strictly a Gronk/Grip thing. It's not a real CDDB field. YHBW." (interactive) (save-excursion (goto-char (point-min)) (let ((year "") nyear) (if (re-search-forward "^DYEAR=\\(.*\\)$" (point-max) t) (setq year (buffer-substring (match-beginning 1) (match-end 1)))) (setq nyear (cddb-read-from-minibuffer "Year: " (cons year 0) nil nil nil year nil)) (if (string= year nyear) () (if (markerp (match-beginning 1)) (progn (delete-region (match-beginning 1) (match-end 1)) (goto-char (match-beginning 1))) (setq nyear (concat nyear "\n")) (goto-char (point-max)) (or (eolp) (insert "\n")) (insert "DYEAR=")) (insert nyear))))) (defun cddb-edit-tracklist() "Edit the tracklist" (interactive) (save-excursion (goto-char (point-min)) (while (re-search-forward "^TTITLE\\([0-9]+\\)=\\(.*\\)$" (point-max) t) (let ((p (buffer-substring (match-beginning 2) (match-end 2))) (tnum (buffer-substring (match-beginning 1) (match-end 1))) n) (setq n (cddb-read-from-minibuffer (concat "Rename " p " to: ") (cons p 0) nil nil nil p nil)) (if (string= p n) () (delete-region (match-beginning 2) (match-end 2)) (goto-char (match-beginning 2)) (insert n)) (forward-char 1) (if cddb-grip-multi-artist (let (a na) (if (looking-at "^TARTIST[0-9]+=\\(.*\\)$") (setq a (buffer-substring (match-beginning 1) (match-end 1))) (setq a "")) (setq na (cddb-read-from-minibuffer (concat "Artist for " n ": ") a nil nil nil a nil)) (if (string= a na) () (if (looking-at "^TARTIST[0-9]+=\\(.*\\)") (progn (delete-region (match-beginning 0) (match-end 0)) (goto-char (match-beginning 0)))) (insert (concat "TARTIST" tnum "=" na)) (or (looking-at "\n") (insert "\n"))))))))) (defun cddb-edit-extras() "Edit the EXTTNN fields" (interactive) (save-excursion (goto-char (point-min)) (while (re-search-forward "^EXTT\\([0-9]+\\)=\\(.*\\)$" (point-max) t) (let ((p (buffer-substring (match-beginning 2) (match-end 2))) (tnum (buffer-substring (match-beginning 1) (match-end 1))) n) (setq n (cddb-read-from-minibuffer (format "Extended information for track %d: " (+ 1 (string-to-int tnum))) (cons p 0) nil nil nil p nil)) (if (string= p n) () (delete-region (match-beginning 2) (match-end 2)) (goto-char (match-beginning 2)) (insert n)))))) (defun cddb-edit() "Edit the current buffer as a CDDB file" (interactive) (message "Editing entire buffer") (cddb-edit-artist) (cddb-edit-genre) (cddb-edit-year) (cddb-edit-tracklist)) ;; Write hook, to increment the revision field (defun cddb-bump-revision() "Bump the revision number of this file" (interactive) (save-excursion (goto-char (point-min)) (let ((revision "") mb1 me1) (if (and (re-search-forward "# Revision: \\(.*\\)$" (point-max) t) (setq mb1 (match-beginning 1) me1 (match-end 1) revision (buffer-substring mb1 me1)) (string-match "^[0-9]+$" revision)) (progn (setq revision (+ 1 (string-to-int revision))) (delete-region mb1 me1) (goto-char mb1) (insert (int-to-string revision))))))) ;;; Local Variables: *** ;;; time-stamp-start:"Last Modified:[ ]+" *** ;;; time-stamp-end: "$" *** ;;; time-stamp-format:"Waider / %02d/%02m/%:y" *** ;;; End: ***