]> git.donarmstrong.com Git - lilypond.git/blob - lilypond-mode.el
patch::: 1.5.9.jcn3
[lilypond.git] / lilypond-mode.el
1 ;;;
2 ;;; lilypond-mode.el --- Major mode for editing GNU LilyPond music scores
3 ;;;
4 ;;; source file of the GNU LilyPond music typesetter
5 ;;; 
6 ;;; (c) 1999--2001 Jan Nieuwenhuizen <janneke@gnu.org>
7 ;;; 
8 ;;; Changed 29th Aug 2001 Heikki Junes <heikki.junes@hut.fi>
9 ;;;    * Add PS-compilation, PS-viewing and MIDI-play
10
11 ;;; Inspired on auctex
12
13 ;;;
14 ;;; Add this to your ~/.emacs or ~/.emacs.el
15 ;;;     (load-library "lilypond-mode.el")
16 ;;;     (setq auto-mode-alist
17 ;;;      (append '(("\\.ly$" . LilyPond-mode) auto-mode-alist)))
18 ;;; 
19
20 (load-library "lilypond-font-lock")
21
22 (require 'easymenu)
23 (require 'compile)
24
25 (defconst LilyPond-version "1.3.143"
26   "`LilyPond-mode' version number.")
27
28 (defconst LilyPond-help-address "bug-gnu-music@gnu.org"
29   "Address accepting submission of bug reports.")
30
31 (defvar LilyPond-mode-hook nil
32   "*Hook called by `LilyPond-mode'.")
33
34 (defvar LilyPond-kick-xdvi nil
35   "If true, no simultaneous xdvi's are started, but reload signal is sent.")
36
37 (defvar LilyPond-command-history nil
38   "Command history list.")
39         
40 (defvar LilyPond-regexp-alist
41   '(("\\([a-zA-Z]?:?[^:( \t\n]+\\)[:( \t]+\\([0-9]+\\)[:) \t]" 1 2))
42   "Regexp used to match LilyPond errors.  See `compilation-error-regexp-alist'.")
43
44 (defcustom LilyPond-include-path ".:/tmp"
45   "* LilyPond include path."
46   :type 'string
47   :group 'LilyPond)
48
49
50 (defun LilyPond-check-files (derived originals extensions)
51   "Check that DERIVED is newer than any of the ORIGINALS.
52 Try each original with each member of EXTENSIONS, in all directories
53 in LilyPond-include-path."
54   (let ((found nil)
55         (regexp (concat "\\`\\("
56                         (mapconcat (function (lambda (dir)
57                                       (regexp-quote (expand-file-name dir))))
58                                    LilyPond-include-path "\\|")
59                         "\\).*\\("
60                         (mapconcat 'regexp-quote originals "\\|")
61                         "\\)\\.\\("
62                         (mapconcat 'regexp-quote extensions "\\|")
63                         "\\)\\'"))
64         (buffers (buffer-list)))
65     (while buffers
66       (let* ((buffer (car buffers))
67              (name (buffer-file-name buffer)))
68         (setq buffers (cdr buffers))
69         (if (and name (string-match regexp name))
70             (progn
71               (and (buffer-modified-p buffer)
72                    (or (not LilyPond-save-query)
73                        (y-or-n-p (concat "Save file "
74                                          (buffer-file-name buffer)
75                                          "? ")))
76                    (save-excursion (set-buffer buffer) (save-buffer)))
77               (if (file-newer-than-file-p name derived)
78                   (setq found t))))))
79     found))
80
81 (defun LilyPond-running ()
82   (let ((process (get-process "lilypond")))
83   (and process
84        (eq (process-status process) 'run))))
85
86 (defun LilyPond-kill-job ()
87   "Kill the currently running LilyPond job."
88   (interactive)
89   ;; What bout TeX, Xdvi?
90   (quit-process (get-process "lilypond") t))
91
92 ;; URG, should only run LilyPond-compile for LilyPond
93 ;; not for tex,xdvi (ly2dvi?)
94 (defun LilyPond-compile-file (command name)
95   ;; We maybe should know what we run here (Lily, ly2dvi, tex)
96   ;; and adjust our error-matching regex ?
97   (compile-internal command "No more errors" name ))
98
99 ;; do we still need this, now that we're using compile-internal?
100 (defun LilyPond-save-buffer ()
101   (if (buffer-modified-p) (save-buffer)))
102
103 ;;; return (dir base ext)
104 (defun split-file-name (name)
105   (let* ((i (string-match "[^/]*$" name))
106          (dir (if (> i 0) (substring name 0 i) "./"))
107          (file (substring name i (length name)))
108          (i (string-match "[^.]*$" file)))
109     (if (and
110          (> i 0)
111          (< i (length file)))
112         (list dir (substring file 0 (- i 1)) (substring file i (length file)))
113       (list dir file ""))))
114
115
116 ;; Should check whether in command-alist?
117 (defcustom LilyPond-command-default "LilyPond"
118   "Default command. Must identify a member of LilyPond-command-alist."
119
120   :group 'LilyPond
121   :type 'string)
122 ;;;(make-variable-buffer-local 'LilyPond-command-last)
123
124 (defvar LilyPond-command-current 'LilyPond-command-master)
125 ;;;(make-variable-buffer-local 'LilyPond-command-master)
126
127
128 ;; If non-nil, LilyPond-command-query will return the value of this
129 ;; variable instead of quering the user. 
130 (defvar LilyPond-command-force nil)
131
132 (defcustom LilyPond-xdvi-command "xdvik"
133   "Command used to display DVI files."
134
135   :group 'LilyPond
136   :type 'string)
137
138 (defcustom LilyPond-gv-command "gv -watch"
139   "Command used to display PS files."
140
141   :group 'LilyPond
142   :type 'string)
143
144 (defcustom LilyPond-midi-command "timidity"
145   "Command used to play MIDI files."
146
147   :group 'LilyPond
148   :type 'string)
149
150 ;; This is the major configuration variable.
151 (defcustom LilyPond-command-alist
152   `(
153     ("LilyPond" . ("lilypond %s" . "TeX"))
154     ("TeX" . ("tex '\\nonstopmode\\input %t'" . "View"))
155
156     ("2Dvi" . ("ly2dvi %s" . "View"))
157     ("2PS" . ("ly2dvi -P %s" . "View"))
158
159     ("Book" . ("lilypond-book %x" . "LaTeX"))
160     ("LaTeX" . ("latex '\\nonstopmode\\input %l'" . "View"))
161
162     ;; point-n-click (arg: exits upop USR1)
163     ("SmartView" . ("xdvi %d" . "LilyPond"))
164     
165     ;; refreshes when kicked USR1
166     ("View" . (,(concat LilyPond-xdvi-command " %d") . "LilyPond"))
167
168     ("ViewPS" . (,(concat LilyPond-gv-command " %p") . "LilyPond"))
169
170     ("Midi" . (,(concat LilyPond-midi-command " %m") . "LilyPond"))
171     )
172
173   "AList of commands to execute on the current document.
174
175 The key is the name of the command as it will be presented to the
176 user, the value is a cons of the command string handed to the shell
177 after being expanded, and the next command to be executed upon
178 success.  The expansion is done using the information found in
179 LilyPond-expand-list.
180 "
181   :group 'LilyPond
182   :type '(repeat (cons :tag "Command Item"
183                        (string :tag "Key")
184                        (cons :tag "How"
185                         (string :tag "Command")
186                         (string :tag "Next Key")))))
187
188 ;; drop this?
189 (defcustom LilyPond-file-extensions '(".ly" ".sly" ".fly")
190   "*File extensions used by manually generated TeX files."
191   :group 'LilyPond
192   :type '(repeat (string :format "%v")))
193
194
195 (defcustom LilyPond-expand-alist 
196   '(
197     ("%s" . ".ly")
198     ("%t" . ".tex")
199     ("%d" . ".dvi")
200     ("%p" . ".ps")
201     ("%l" . ".latex")
202     ("%x" . ".tely")
203     ("%m" . ".midi")
204     )
205     
206   "Alist of expansion strings for LilyPond command names."
207   :group 'LilyPond
208   :type '(repeat (cons :tag "Alist item"
209                   (string :tag "Symbol")
210                   (string :tag "Expansion")))) 
211
212
213 (defcustom LilyPond-command-Show "View"
214   "*The default command to show (view or print) a LilyPond file.
215 Must be the car of an entry in `LilyPond-command-alist'."
216   :group 'LilyPond
217   :type 'string)
218   (make-variable-buffer-local 'LilyPond-command-Show)
219
220 (defcustom LilyPond-command-Print "Print"
221   "The name of the Print entry in LilyPond-command-Print."
222   :group 'LilyPond
223   :type 'string)
224
225 (defun xLilyPond-compile-sentinel (process msg)
226   (if (and process
227            (= 0 (process-exit-status process)))
228       (setq LilyPond-command-default
229               (cddr (assoc LilyPond-command-default LilyPond-command-alist)))))
230
231 ;; FIXME: shouldn't do this for stray View/xdvi
232 (defun LilyPond-compile-sentinel (buffer msg)
233   (if (string-match "^finished" msg)
234       (setq LilyPond-command-default
235             (cddr (assoc LilyPond-command-default LilyPond-command-alist)))))
236
237 ;;(make-variable-buffer-local 'compilation-finish-function)
238 (setq compilation-finish-function 'LilyPond-compile-sentinel)
239
240 (defun LilyPond-command-query (name)
241   "Query the user for what LilyPond command to use."
242   (let* ((default (cond ((if (string-equal name "emacs-lily")
243                              (LilyPond-check-files (concat name ".tex")
244                                                    (list name)
245                                                    LilyPond-file-extensions)
246                            ;; FIXME
247                            (LilyPond-save-buffer)
248                            ;;"LilyPond"
249                            LilyPond-command-default))
250                         (t LilyPond-command-default)))
251          
252          (answer (or LilyPond-command-force
253                      (completing-read
254                       (concat "Command: (default " default ") ")
255                       LilyPond-command-alist nil t nil 'LilyPond-command-history))))
256
257     ;; If the answer is "LilyPond" it will not be expanded to "LilyPond"
258     (let ((answer (car-safe (assoc answer LilyPond-command-alist))))
259       (if (and answer
260                (not (string-equal answer "")))
261           answer
262         default))))
263
264
265 ;; FIXME: find ``\score'' in buffers / make settable?
266 (defun LilyPond-master-file ()
267   ;; duh
268   (buffer-file-name))
269
270 (defun LilyPond-command-master ()
271   "Run command on the current document."
272   (interactive)
273   (LilyPond-command (LilyPond-command-query (LilyPond-master-file))
274                     'LilyPond-master-file))
275
276 ;; FIXME, this is broken
277 (defun LilyPond-region-file (begin end)
278   (let (
279         ;; (dir "/tmp/")
280         ;; urg
281         (dir "./")
282         (base "emacs-lily")
283         ;; Hmm
284         (ext (if (string-match "^[\\]score" (buffer-substring begin end))
285                  ".ly"
286                (if (< 50 (abs (- begin end)))
287                    ".fly"
288                  ".sly"))))
289     (concat dir base ext)))
290
291 (defun LilyPond-command-region (begin end)
292   "Run LilyPond on the current region."
293   (interactive "r")
294   (write-region begin end (LilyPond-region-file begin end) nil 'nomsg)
295   (LilyPond-command (LilyPond-command-query
296                      (LilyPond-region-file begin end))
297                     '(lambda () (LilyPond-region-file begin end))))
298
299 (defun LilyPond-command-buffer ()
300   "Run LilyPond on buffer."
301   (interactive)
302   (LilyPond-command-region (point-min) (point-max)))
303
304 (defun LilyPond-command-expand (string file)
305   (let ((case-fold-search nil))
306     (if (string-match "%" string)
307         (let* ((b (match-beginning 0))
308                (e (+ b 2))
309                (l (split-file-name file))
310                (dir (car l))
311                (base (cadr l)))
312           (LilyPond-command-expand
313            (concat (substring string 0 b)
314                    dir
315                    base
316                    (let ((entry (assoc (substring string b e)
317                                        LilyPond-expand-alist)))
318                      (if entry (cdr entry) ""))
319                    (substring string e))
320            file))
321       string)))
322
323 (defun LilyPond-shell-process (name buffer command)
324   (let ((old (current-buffer)))
325     (switch-to-buffer-other-window buffer)
326     ;; If we empty the buffer don't see messages scroll by.
327     ;; (erase-buffer)
328     
329     (start-process-shell-command name buffer command)
330     (switch-to-buffer-other-window old)))
331   
332
333 (defun LilyPond-command (name file)
334   "Run command NAME on the file you get by calling FILE.
335
336 FILE is a function return a file name.  It has one optional argument,
337 the extension to use on the file.
338
339 Use the information in LilyPond-command-alist to determine how to run the
340 command."
341   
342   (let ((entry (assoc name LilyPond-command-alist)))
343     (if entry
344         (let ((command (LilyPond-command-expand (cadr entry)
345                                                 (apply file nil))))
346           (if (string-equal name "View")
347               (let ((buffer-xdvi (get-buffer-create "*view*")))
348                 (if LilyPond-kick-xdvi
349                   (let ((process-xdvi (get-buffer-process buffer-xdvi)))
350                     (if process-xdvi
351                         (signal-process (process-id process-xdvi) 'SIGUSR1)
352                       (LilyPond-shell-process name buffer-xdvi command)))
353                   (LilyPond-shell-process name buffer-xdvi command)))
354             (progn
355               (setq LilyPond-command-default name)
356               (LilyPond-compile-file command name)))))))
357           
358 ;; XEmacs stuff
359 ;; Sadly we need this for a macro in Emacs 19.
360 (eval-when-compile
361   ;; Imenu isn't used in XEmacs, so just ignore load errors.
362   (condition-case ()
363       (require 'imenu)
364     (error nil)))
365
366
367 ;;; Keymap
368
369 (defvar LilyPond-mode-map ()
370   "Keymap used in `LilyPond-mode' buffers.")
371
372 ;; Note:  if you make changes to the map, you must do
373 ;;    M-x set-variable LilyPond-mode-map nil
374 ;;    M-x eval-buffer
375 ;;    M-x LilyPond-mode
376 ;; to let the changest take effect
377
378 (if LilyPond-mode-map
379     ()
380   (setq LilyPond-mode-map (make-sparse-keymap))
381   (define-key LilyPond-mode-map "\C-c\C-r" 'LilyPond-command-region)
382   (define-key LilyPond-mode-map "\C-c\C-b" 'LilyPond-command-buffer)
383   (define-key LilyPond-mode-map "\C-c\C-k" 'LilyPond-kill-job)
384   (define-key LilyPond-mode-map "\C-c\C-c" 'LilyPond-command-master)
385   )
386
387 ;;; Menu Support
388
389 (defun LilyPond-command-menu-entry (entry)
390   ;; Return LilyPond-command-alist ENTRY as a menu item.
391   (let ((name (car entry)))
392     (cond ((and (string-equal name LilyPond-command-Print)
393                 LilyPond-printer-list)
394            (let ((command LilyPond-print-command)
395                  (lookup 1))
396              (append (list LilyPond-command-Print)
397                      (mapcar 'LilyPond-command-menu-printer-entry
398                              LilyPond-printer-list))))
399           (t
400            (vector name (list 'LilyPond-command-menu name) t)))))
401
402
403 (easy-menu-define LilyPond-mode-menu
404     LilyPond-mode-map
405     "Menu used in LilyPond mode."
406   (append '("Command")
407           '(("Command on"
408              [ "Master File" LilyPond-command-select-master
409                :keys "C-c C-c" :style radio
410                :selected (eq LilyPond-command-current 'LilyPond-command-master) ]
411              [ "Buffer" LilyPond-command-select-buffer
412                :keys "C-c C-b" :style radio
413                :selected (eq LilyPond-command-current 'LilyPond-command-buffer) ]
414              [ "Region" LilyPond-command-select-region
415                :keys "C-c C-r" :style radio
416                :selected (eq LilyPond-command-current 'LilyPond-command-region) ]))
417           (let ((file 'LilyPond-command-on-current))
418             (mapcar 'LilyPond-command-menu-entry LilyPond-command-alist))))
419
420
421 (defconst LilyPond-imenu-generic-re "^\\([a-zA-Z_][a-zA-Z0-9_]*\\) *="
422   "Regexp matching Identifier definitions.")
423
424 (defvar LilyPond-imenu-generic-expression
425   (list (list nil LilyPond-imenu-generic-re 1))
426   "Expression for imenu")
427
428 (defun LilyPond-command-select-master ()
429   (interactive)
430   (message "Next command will be on the master file")
431   (setq LilyPond-command-current 'LilyPond-command-master))
432
433 (defun LilyPond-command-select-buffer ()
434   (interactive)
435   (message "Next command will be on the buffer")
436   (setq LilyPond-command-current 'LilyPond-command-buffer))
437
438 (defun LilyPond-command-select-region ()
439   (interactive)
440   (message "Next command will be on the region")
441   (setq LilyPond-command-current 'LilPond-command-region))
442
443 (defun LilyPond-command-menu (name)
444   ;; Execute LilyPond-command-alist NAME from a menu.
445   (let ((LilyPond-command-force name))
446     (funcall LilyPond-command-current)))
447
448 (defun LilyPond-mode ()
449   "Major mode for editing LilyPond music files.
450
451 This mode knows about LilyPond keywords and line comments, not about
452 indentation or block comments.  It features easy compilation, error
453 finding and viewing of a LilyPond source buffer or region.
454
455 COMMANDS
456 \\{LilyPond-mode-map}
457 VARIABLES
458
459 LilyPond-command-alist\t\talist from name to command
460 LilyPond-xdvi-command\t\tcommand to display dvi files -- bit superfluous"
461   (interactive)
462   ;; set up local variables
463   (kill-all-local-variables)
464
465   (make-local-variable 'font-lock-defaults)
466   (setq font-lock-defaults '(LilyPond-font-lock-keywords))
467
468   (make-local-variable 'paragraph-separate)
469   (setq paragraph-separate "^[ \t]*$")
470
471   (make-local-variable 'paragraph-start)
472   (setq paragraph-start "^[ \t]*$")
473
474   (make-local-variable 'comment-start)
475   (setq comment-start "%")
476
477   (make-local-variable 'comment-start-skip)
478   (setq comment-start-skip "%{? *")
479
480   (make-local-variable 'comment-end)
481   (setq comment-end "")
482
483   (make-local-variable 'block-comment-start)
484   (setq block-comment-start "%{")
485
486   (make-local-variable 'block-comment-end)  
487   (setq block-comment-end   "%}")
488
489   (make-local-variable 'indent-line-function)
490   (setq indent-line-function 'indent-relative-maybe)
491  
492     (set-syntax-table LilyPond-mode-syntax-table)
493   (setq major-mode 'LilyPond-mode)
494   (setq mode-name "LilyPond")
495   (setq local-abbrev-table LilyPond-mode-abbrev-table)
496   (use-local-map LilyPond-mode-map)
497
498   (make-local-variable 'imenu-generic-expression)
499   (setq imenu-generic-expression LilyPond-imenu-generic-expression)
500   (imenu-add-to-menubar "Index")
501
502     ;; run the mode hook. LilyPond-mode-hook use is deprecated
503   (run-hooks 'LilyPond-mode-hook))
504
505 (defun LilyPond-version ()
506   "Echo the current version of `LilyPond-mode' in the minibuffer."
507   (interactive)
508   (message "Using `LilyPond-mode' version %s" LilyPond-version))
509
510 (provide 'lilypond-mode)
511 ;;; lilypond-mode.el ends here
512