-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathgimme.el
252 lines (213 loc) · 11.8 KB
/
gimme.el
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
;;; gimme.el --- GIMME Interesting Music on My Emacs
;; Author: Konrad Scorciapino <konr@konr.mobi>
;; Keywords: XMMS2, mp3
;; 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 2, 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 GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
;;; Commentary
;; GIMME (GIMME Interesting Music on My Emacs) is an XMMS2 client
;; originally developed for Google's Summer of Code. Kudos to them
;; and to DraX for the Support.
;; GIMME works by using collections as search results, and its
;; multiple views allows you to do that in multiple ways. Check out
;; more at http://gimmeplayer.org
;;; Code
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Variables used in GIMME ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defvar gimme-process nil "Reference to the ruby process")
(defvar gimme-help-function-groups nil "Variable that holds extra information on functions")
(defvar gimme-filter-remainder "" "Variable used to hold incomplete sexps received from the ruby process")
(defvar gimme-path (expand-file-name (file-name-directory (or load-file-name buffer-file-name))) "The fullname of the ruby file")
(defvar gimme-bookmark-minimal-collection-list '(((0 ("reference" "All Media" "title" "All Media") nil))) "The minimal collection you must have access to, which is just the Universe collection.")
(defvar gimme-anonymous-collections gimme-bookmark-minimal-collection-list "The list to which the collections you create while explore mlib will go.")
(defvar gimme-playtime nil "Variable used to hold the current track's duration and playtime")
(defvar gimme-mlib-cache-plist nil "Contains cached information on the current collection")
(defvar gimme-mlib-cache-global nil "Contains cached information on the universal collection")
(defvar gimme-autocomplete-prompt "> " "The prompt an user gets when asking for completion. Internal variable used to avoid passing a needed parameter to every function around")
(defvar gimme-autocomplete-done-with-autocompletions t "Internal variable, set when the user stops tabbing to autocomplete")
(defvar gimme-autocomplete-current-coll nil "Internal variable, used to avoid passing a needed parameter to every function around.")
(defvar gimme-augmented-info-buffer-name "GIMME - Information on" "Prefix of the buffer used to show information of an artist.")
(defvar gimme-autocomplete-buffer-name "*GIMME Completions*" "The name of the buffer used to show possible candidates for completion.")
(defvar gimme-bookmark-name "GIMME - Bookmarks" "The name of the buffer used to show your collections.")
(defvar gimme-inspect-buffer-name "GIMME - Inspect" "The name of the buffer used to inspect hashes")
(defvar gimme-tagwriter-buffer-name "GIMME - Tagwriter" "The name of the buffer used to write tags in a fancier way")
(defvar gimme-bookmark-facets
'("genre" "artist" "album" "timesplayed") "The facets of the collections that are worth displaying.")
(defvar gimme-inspect-max-length 50 "Maximum length of the string that will be used in every field of gimme-inspect")
(defvar gimme-tagwriter-max-length 33 "Maximum length of the string that will be used in every field of gimme-tagwriter")
(defvar gimme-debug 0
"To debug.
0: Do nothing extra
1: Prints the functions being called by the ruby process
2: Print the whole sexps
3: Print the whole sexps and do not evaluate them")
;;;;;;;;;;;;;;
;; Adapters ;;
;;;;;;;;;;;;;;
(defvar gimme-adapters '(xmms2 (exec "ruby %s/xmms2.rb")
mplayer (exec "ruby %s/mplayer.rb"))
"Available adapters")
(defvar gimme-default-adapter 'xmms2 "The default adapter")
;;;;;;;;;;;;;;;
;; Functions ;;
;;;;;;;;;;;;;;;
(defun gimme-buffers (&optional function)
"Get all buffers used by GIMME. FIXME: mode"
(let* ((modes '(gimme-playlist-mode gimme-collection-mode gimme-bookmark-mode))
(buffers (remove-if-not (lambda (buf) (member (major-mode buf) modes))
(buffer-list)))
(filtered (remove-if-not function buffers)))
filtered))
(defun gimme-first-buffer-with-vars (&rest plist)
"Gets a GIMME buffer matching the variables of a plist"
(let* ((all (gimme-buffers)) (alist (plist-to-alist plist))
(consed (mapcar (lambda (el) (cons el (buffer-local-variables el))) all)))
(caar (reduce (lambda (coll pair)
(remove-if-not (lambda (el) (equal (cdr pair)
(cdr (assoc (car pair) (cdr el)))))
coll)) alist :initial-value consed))))
(defun gimme-gen-buffer (plist)
"Generates a Buffer and sets the variables correctly"
(let* ((type (getf plist 'gimme-buffer-type))
(type-s (case type ('collection "Collection") ('playlist "Playlist")
('lyrics "Lyrics")))
(name-s (case type
('collection (getf plist 'gimme-collection-title))
('playlist (getf plist 'gimme-playlist-name))
('lyrics (getf plist 'title))))
(buffer-name (decode-coding-string
(format "GIMME - %s (%s)" type-s name-s) 'utf-8))
(facet (plist-get plist 'gimme-collection-facet))
(setup-plist `(ecoli-separator "\n" ecoli-format-function gimme-string))
(plist (append plist `(buffer-name ,buffer-name ecoli-formats ,gimme-playlist-formats)))
(function (lambda ()
(when facet (insert (format "Collection: %s\nFacet: %s\n---\n\n"
(propertize name-s 'font-lock-face `(:foreground ,(color-for name-s)))
(propertize facet 'font-lock-face `(:weight bold)))))
(funcall (case type (playlist #'gimme-playlist-mode)
(collection (lambda () (gimme-collection-mode facet))))))))
(ecoli-gen-buffer plist setup-plist function)))
(defun gimme-extract-needed-tags ()
"Informs the ruby client of all %variables required by the config file"
(let* ((l (flatten gimme-playlist-formats))
(l (remove-if-not (lambda (n) (and (symbolp n)
(string-match "^%" (format "%s" n)))) l))
(l (mapcar (lambda (n) (substring (format "%s" n) 1))
(remove-duplicates l))))
l))
(defun gimme-eval-all-sexps (s)
"Evaluates all sexps from the string. As it will probably encounter a broken sexp, a variable is used to store the remainder to be used in future calls"
(let ((s (decode-coding-string (concat gimme-filter-remainder s) 'utf-8)))
(setq gimme-filter-remainder
(loop for x = (ignore-errors (read-from-string s))
then (ignore-errors (read-from-string (substring s position)))
while x
summing (or (cdr x) 0) into position
doing (let* ((s (decode-strings-in-tree (car x) 'utf-8))
(f (caar x)))
(when (> gimme-debug 0)
(message "GIMME: %s" (if (>= gimme-debug 2) s f)))
(when (> 3 gimme-debug)
(ignore-errors (eval s))))
finally (return (substring s position))))))
(defun gimme-update-playtime (time max)
"Updates the playtime in the gimme-playtime variable"
(setq gimme-playtime `((time . ,time) (max . ,(if (numberp max) max time)))))
(defmacro gimme-on-buffer (name &rest body)
"Macro that does all the nasty things required to modify a buffer"
`(with-current-buffer (get-buffer-create ,name)
(unlocking-buffer (save-excursion (goto-char (point-max)) ,@body))))
(defun gimme-init ()
"Creates the buffer and manages the processes"
(dolist (proc (remove-if-not (lambda (el) (string-match "GIMME" el)) (mapcar #'process-name (process-list))))
(kill-process proc))
(let* ((adapter (plist-get gimme-adapters gimme-default-adapter))
(path (format (plist-get adapter 'exec) gimme-path)))
(setq gimme-process (start-process-shell-command "GIMME" nil path)))
(set-process-filter gimme-process (lambda (a b) (gimme-eval-all-sexps b))))
(defun gimme-send-message (&rest args)
"Formats the arguments using (format) then sends the resulting string to the process."
(let* ((message (apply #'format args))
(message (replace-regexp-in-string "%" "%%" message)))
(when (> gimme-debug 0) (message message))
(process-send-string gimme-process message)))
(defun gimme-string (plist)
"Receives a song represented as a plist and binds each key as %key to be used by the formatting functions at gimme-playlist-formats"
(let ((plist (plist-put plist 'font-lock-face nil)))
(eval `(let ((plist ',plist)
,@(mapcar (lambda (n) (list (intern (format "%%%s" (car n)))
(if (and (symbolp (cdr n)) (not (null (cdr n))))
(list 'quote (cdr n)) (cdr n))))
(plist-to-alist plist)))
(eval (car ecoli-formats))))))
(defun gimme-coll-overview (name data)
"Caches the data present in a collection."
(if name (setq gimme-mlib-cache-plist (plist-put gimme-mlib-cache-plist name data))
(setq gimme-mlib-cache-global data)))
(defun gimme-try-the-best-to-ensure-fancy-features ()
"Makes sure that GIMME will be able to do at least a general-scoped autocompletion and that the help features will be loaded."
(unless gimme-mlib-cache-global (gimme-send-message "(coll_overview)\n"))
(gimme-send-message "(get_help_info)\n"))
(defun gimme-make-basic-map ()
"Generates a map with common, basic functionalities."
(let ((map (make-sparse-keymap)))
(suppress-keymap map t)
(define-key map (kbd "q") 'kill-current-buffer)
(define-key map (kbd "j") 'next-line)
(define-key map (kbd "k") 'previous-line)
(define-key map (kbd "C-f") 'scroll-up)
(define-key map (kbd "C-b") 'scroll-down)
(define-key map (kbd "O") 'gimme-get-conf)
(define-key map (kbd "?") 'gimme-help-show-keybindings)
(define-key map (kbd "=") 'gimme-increase-volume)
(define-key map (kbd "+") 'gimme-increase-volume)
(define-key map (kbd "-") 'gimme-decrease-volume)
map))
(defun gimme ()
"The XMMS2 interface we all love."
(interactive)
(setq gimme-filter-remainder "")
(gimme-init)
;(message (format "(set_atribs %s)\n" (gimme-extract-needed-tags)))
(gimme-send-message (format "(set_atribs %s)\n" (gimme-extract-needed-tags)))
(gimme-playlist)
(gimme-try-the-best-to-ensure-fancy-features)
)
;;;;;;;;;;;;;
;; Bundled ;;
;;;;;;;;;;;;;
(require 'cl)
(loop for bundled in '(hexrgb htmlr ecoli) doing
(or (require bundled nil t)
(let ((load-path
(cons (expand-file-name "fallback-libs" (file-name-directory (or load-file-name buffer-file-name)))
load-path)))
(require bundled))))
;;;;;;;;;;;;;;
;; Subparts ;;
;;;;;;;;;;;;;;
(require 'gimme-augmented)
(require 'gimme-autocomplete)
(require 'gimme-bookmark)
(require 'gimme-collection)
(require 'gimme-emacs)
(require 'gimme-eq)
(require 'gimme-custom)
(require 'gimme-help)
(require 'gimme-inspect)
(require 'gimme-playlist)
(require 'gimme-status-mode)
(require 'gimme-tagwriter)
(require 'gimme-utils)
(provide 'gimme)
;;; gimme.el ends here