]> git.donarmstrong.com Git - lilypond.git/blob - lilypond-mode.el
release: 1.3.19
[lilypond.git] / lilypond-mode.el
1 ;;; lilypond-mode.el --- Major mode for editing GNU LilyPond music scores
2
3 ;; Copyright (C) 1992,1993,1994  Tim Peters
4
5 ;; Author: 1999: Jan Nieuwenhuizen
6 ;; Author: 1997: Han-Wen Nienhuys
7 ;; Author: 1995-1996 Barry A. Warsaw
8 ;;         1992-1994 Tim Peters
9 ;; Created:       Feb 1992
10 ;; Version:       0.0
11 ;; Last Modified: 12SEP97
12 ;; Keywords: mudela languages music
13
14 ;; This software is provided as-is, without express or implied
15 ;; warranty.  Permission to use, copy, modify, distribute or sell this
16 ;; software, without fee, for any purpose and by any individual or
17 ;; organization, is hereby granted, provided that the above copyright
18 ;; notice and this paragraph appear in all copies.
19
20 ;; Sigh
21
22 ;; This is a cannabalised version of python-mode.el (HWN)
23 ;; Added lily-eval-buffer -- jcn
24 ;;
25 ;; TODO: 
26 ;; * lily/ly/lilypond?
27 ;; * fix lily-keymap 
28 ;; * mail Tim Peters about silly copyright (notice)?
29 ;; * syntax
30 ;;   - should handle block comments too.
31 ;;   - handle lexer modes (\header, \melodic, \lyric) etc.
32 ;;   - indentation
33 ;;   - notenames?
34 ;;   - fontlock: \melodic \melodic
35
36
37 (defconst lily-version "1.3.19"
38   "`lilypond-mode' version number.")
39
40 (defconst lily-help-address "hanwen@cs.uu.nl"
41   "Address accepting submission of bug reports.")
42
43 (defconst lily-font-lock-keywords
44   (let* ((keywords '("spanrequest" "simultaneous" "sequential" "accepts"
45                      "alternative" "bar" "breathe"
46                      "cadenza" "chordmodifiers" "chords" "clef" "cm" "consists"
47                      "consistsend" "context"
48                      "duration" "font" "grace" "header" "in" "lyrics"
49                      "key" "keysignature" "mark" "musicalpitch"
50                      "time" "times" "midi" "mm" "name" "notenames"
51                      "notes" "partial" "paper" "penalty" "property" "pt"
52                      "relative" "remove" "repeat" "repetitions" "addlyrics"
53                      "scm" "scmfile" "score" "script"
54                      "shape" "skip" "textscript" "tempo" "translator" "transpose"
55                      "type" "version" 
56                      ))
57        (kwregex (mapconcat (lambda (x) (concat "\\\\" x))  keywords "\\|")))
58
59     (list 
60       (concat ".\\(" kwregex "\\)[^a-zA-Z]")
61       (concat "^\\(" kwregex "\\)[^a-zA-Z]")
62       '(".\\(\\\\[a-zA-Z][a-zA-Z]*\\)" 1 font-lock-variable-name-face)
63       '("^[\t ]*\\([a-zA-Z][_a-zA-Z]*\\) *=" 1 font-lock-variable-name-face)     
64     ))
65   "Additional expressions to highlight in Mudela mode.")
66
67 ;; define a mode-specific abbrev table for those who use such things
68 (defvar lilypond-mode-abbrev-table nil
69   "Abbrev table in use in `lilypond-mode' buffers.")
70
71 (define-abbrev-table 'lilypond-mode-abbrev-table nil)
72
73 (defvar lilypond-mode-hook nil
74   "*Hook called by `lilypond-mode'.")
75
76 (defvar lily-mode-syntax-table nil
77   "Syntax table used in `lilypond-mode' buffers.")
78
79 ;;
80 (if lily-mode-syntax-table
81     ()
82   (setq lily-mode-syntax-table (make-syntax-table))
83   (mapcar (function
84            (lambda (x) (modify-syntax-entry
85                         (car x) (cdr x) lily-mode-syntax-table)))
86           '(( ?\( . "." ) ( ?\) . "." )
87             ( ?\[ . "." ) ( ?\] . "." )
88             ( ?\{ . "(}" ) ( ?\} . "){" )
89             ( ?\< . "(>" )( ?\> . ")>") 
90             ( ?\$ . "." ) ( ?\% . "." ) ( ?\& . "." )
91             ( ?\* . "." ) ( ?\+ . "." ) ( ?\- . "." )
92             ( ?\/ . "." )  ( ?\= . "." )
93             ( ?\| . "." ) (?\\ . "\\" )
94             ( ?\_ . "." )       
95             ( ?\' . "w")        
96             ( ?\" . "\"" )
97             ( ?\% . "<")
98             ( ?\n . ">")
99
100 ; FIXME
101 ;           ( ?%  .  ". 124b" )
102 ;           ( ?{  .  ". 23" )
103             ))
104
105   )     
106
107 (defconst lily-imenu-generic-re "^\\([a-zA-Z_][a-zA-Z0-9_]*\\) *="
108   "Regexp matching Identifier definitions.")
109
110 ;; Sadly we need this for a macro in Emacs 19.
111 (eval-when-compile
112   ;; Imenu isn't used in XEmacs, so just ignore load errors.
113   (condition-case ()
114       (require 'imenu)
115     (error nil)))
116
117 (defvar lily-imenu-generic-expression
118   (list (list nil lily-imenu-generic-re 1))
119   "Expression for imenu")
120
121
122 ;;; we're using some handy compile commands
123 (require 'compile)
124
125 (defcustom lily-command "lilypond"
126   "* LilyPond executable."
127   :type 'string
128   :group 'lily)
129
130 (defcustom lily-parameters ""
131   "*."
132   :type 'string
133   :group 'lily)
134
135 (defvar lily-regexp-alist
136   '(("\\([a-zA-Z]?:?[^:( \t\n]+\\)[:( \t]+\\([0-9]+\\)[:) \t]" 1 2))
137   "Regexp used to match LilyPond errors.  See `compilation-error-regexp-alist'.")
138
139 (defcustom lily-tex-command "tex"
140   "*."
141   :type 'string
142   :group 'lily)
143
144 (defcustom lily-xdvi-command "xdvi"
145   "*."
146   :type 'string
147   :group 'lily)
148
149 (defun lily-compile-file (command parameters file)
150   ;; Setting process-setup-function makes exit-message-function work
151   ;; even when async processes aren't supported.
152   (let ((command-args (concat command " " parameters " " file)))
153         (compile-internal command-args "No more errors" "LilyPond")))
154
155 ;; do we still need this, now that we're using compile-internal?
156 (defun lily-save-buffer ()
157   (if (buffer-modified-p) (save-buffer)))
158
159 ;;; return (dir base ext)
160 (defun split-file-name (name)
161   (let* ((i (string-match "[^/]*$" name))
162          (dir (if (> i 0) (substring name 0 i) "./"))
163          (file (substring name i (length name)))
164          (i (string-match "[^.]*$" file)))
165     (if (and
166          (> i 0)
167          (< i (length file)))
168         (list dir (substring file 0 (- i 1)) (substring file i (length file)))
169       (list dir file ""))))
170
171 ;;;###autoload
172 (defun lily-eval-buffer ()
173   "Run LilyPond on buffer."
174   (interactive)
175   (let ((buffer (buffer-name)))
176     (if buffer-file-name
177         (progn
178           (lily-save-buffer)
179           (lily-compile-file lily-command lily-parameters buffer))
180       (progn
181         (error "Buffer %s is not associated with a file" buffer)
182         (lily-eval-region (min-point) (max-point))))))
183
184 ;;;###autoload
185 (defun lily-eval-region (start end)
186   "Run LilyPond on region."
187   (interactive "r")
188   ;;  (message "saving current buffer to temporary file ...")
189   ;;  (write-file tmp-file-with-directory)
190   ;;(lily-compile-file lily-command "--init=init.fly" "-")
191   (let ((basename "emacs-lily"))
192     (write-region start end (concat basename ".fly") nil 'nomsg)
193     (lily-compile-file lily-command lily-parameters basename)))
194
195 (defun lily-running ()
196   (let ((process (get-process "lilypond")))
197   (and process
198        (eq (process-status process) 'run))))
199
200 (defun lily-tex-file (basename)
201   (call-process lily-tex-command nil t nil basename))
202
203 (defun lily-xdvi-file (basename)
204   (let ((outbuf (get-buffer-create "*lily-xdvi*"))
205         (name "xdvi")
206         (command (concat lily-xdvi-command " " basename)))
207     (if (get-process "xdvi")
208         ;; Don't open new xdvi window, but force redisplay
209         ;; We could make this an option.
210         (signal-process (process-id (get-process "xdvi")) 'SIGUSR1)
211       (if (fboundp 'start-process)
212           (let* ((process-environment (cons "EMACS=t" process-environment))
213                  (proc (start-process-shell-command name outbuf command)))
214             ;;(set-process-sentinel proc 'compilation-sentinel)
215             ;;(set-process-filter proc 'compilation-filter)
216             (set-marker (process-mark proc) (point) outbuf))
217         ;;(setq compilation-in-progress (cons proc compilation-in-progress)))
218         
219         ;; No asynchronous processes available.
220         (message "Executing `%s'..." command)
221         ;; Fake modeline display as if `start-process' were run.
222         (setq mode-line-process ":run")
223         (force-mode-line-update)
224         (sit-for 0)                     ; Force redisplay
225       (call-process shell-file-name nil outbuf nil "-c" command)
226       (message "Executing `%s'...done" command)))))
227       
228
229 ;;;###autoload
230 (defun lily-xdvi-buffer ()
231   "Run LilyPond, TeX and Xdvi on buffer."
232   (interactive)
233
234   (let* ((split (split-file-name buffer-file-name))
235          (dir (car split))
236          (base (cadr split)))
237
238     ;; we don't really need this...
239     (let ((tex (concat dir base ".tex"))
240           (dvi (concat dir base ".dvi")))
241       (if (file-exists-p tex) (delete-file tex))
242       (if (file-exists-p dvi) (delete-file dvi)))
243
244     (lily-eval-buffer)
245     (set-buffer "*lilypond*")
246     
247     ;;(setq default-directory dir)
248     (while (lily-running)
249       (continue-process (get-process "lilypond")))
250     (sit-for 0)                 ; Force redisplay
251     
252     (if (= 0 (process-exit-status (get-process "lilypond")))
253         (progn
254           (if (= 0 (lily-tex-file base))
255               (lily-xdvi-file base))))))
256   
257 ;;;###autoload
258 (defun lily-xdvi-region (start end)
259   "Run LilyPond, TeX and Xdvi on region."
260   (interactive "r")
261
262   (let ((dir default-directory)
263         (base "emacs-lily"))
264
265     ;; we don't really need this...
266     (let ((tex (concat dir base ".tex"))
267           (dvi (concat dir base ".dvi")))
268       (if (file-exists-p tex) (delete-file tex))
269       (if (file-exists-p dvi) (delete-file dvi)))
270     
271     (lily-eval-region start end)
272     (set-buffer "*lilypond*")
273     
274     ;;(setq default-directory dir)
275     (while (lily-running)
276       (continue-process (get-process "lilypond")))
277     (sit-for 0)                 ; Force redisplay
278     
279     (if (= 0 (process-exit-status (get-process "lilypond")))
280         (progn
281           (if (= 0 (lily-tex-file base))
282               (lily-xdvi-file base))))))
283
284 ;;;###autoload
285 (defun lily-kill-job ()
286   "Kill the currently running LilyPond job."
287   (interactive)
288   (quit-process (get-process "lilypond") t))
289
290 ;; hmm
291 ;;  (kill-process (get-process "xdvi") t)
292
293 (defvar lily-mode-map ()
294   "Keymap used in `lilypond-mode' buffers.")
295
296 (if lily-mode-map
297     ()
298   (setq lily-mode-map (make-sparse-keymap))
299   ;; this doesn't work, here
300   ;; I would very much like to have [f9], globally defined as 'compile,
301   ;; being overidden to 'lily-eval-buffer for LilyPond buffers
302   (define-key lily-mode-map [C-f9] 'lily-eval-buffer)
303   ;; urg
304   ;; add to .emacs:
305   ;; (global-set-key [C-f9] 'lily-eval-buffer)
306   ) 
307
308 (defun lilypond-mode ()
309   "Major mode for editing Mudela files."
310   (interactive)
311   ;; set up local variables
312   (kill-all-local-variables)
313
314   (make-local-variable 'font-lock-defaults)
315   (setq font-lock-defaults '(lily-font-lock-keywords))
316
317   (make-local-variable 'paragraph-separate)
318   (setq paragraph-separate "^[ \t]*$")
319
320   (make-local-variable 'paragraph-start)
321   (setq paragraph-start "^[ \t]*$")
322
323   (make-local-variable 'comment-start)
324   (setq comment-start "%")
325
326   (make-local-variable 'comment-start-skip)
327   (setq comment-start-skip "%{? *")
328
329   (make-local-variable 'comment-end)
330   (setq comment-end "\n")
331
332   (make-local-variable 'block-comment-start)
333   (setq block-comment-start "%{")
334
335   (make-local-variable 'block-comment-end)  
336   (setq block-comment-end   "%}")
337
338   ;; (make-local-variable 'comment-column)
339   ;; (setq comment-column 40)
340
341   (make-local-variable 'imenu-generic-expression)
342   (setq imenu-generic-expression lily-imenu-generic-expression)
343
344   (make-local-variable 'indent-line-function)
345   (setq indent-line-function 'indent-relative-maybe)
346  
347   ;;
348   (set-syntax-table lily-mode-syntax-table)
349   (setq major-mode 'lilypond-mode)
350   (setq mode-name "Mudela")
351   (setq local-abbrev-table lilypond-mode-abbrev-table)
352   (use-local-map lily-mode-map)
353
354   ;; run the mode hook. lily-mode-hook use is deprecated
355   (run-hooks 'lilypond-mode-hook))
356
357
358 (defun lily-keep-region-active ()
359   ;; do whatever is necessary to keep the region active in XEmacs.
360   ;; Ignore byte-compiler warnings you might see.  Also note that
361   ;; FSF's Emacs 19 does it differently and doesn't its policy doesn't
362   ;; require us to take explicit action.
363   (and (boundp 'zmacs-region-stays)
364        (setq zmacs-region-stays t)))
365
366
367 ;;(defun lily-comment-region (beg end &optional arg)
368 ;;  "Like `comment-region' but uses double hash (`#') comment starter."
369 ;;  (interactive "r\nP")
370 ;;  (let ((comment-start lily-block-comment-prefix))
371 ;;    (comment-region beg end arg)))
372 \f
373 (defun lily-version ()
374   "Echo the current version of `lilypond-mode' in the minibuffer."
375   (interactive)
376   (message "Using `lilypond-mode' version %s" lily-version)
377   (lily-keep-region-active))
378
379 (provide 'lilypond-mode)
380 ;;; lilypond-mode.el ends here