]> git.donarmstrong.com Git - lilypond.git/blob - lilypond-mode.el
release: 1.3.27
[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 ;; This started out as a cannabalised version of python-mode.el, by hwn
21 ;; For changes see the LilyPond ChangeLog
22 ;;
23 ;; TODO: 
24 ;; * lily/ly/lilypond?
25 ;; * syntax
26 ;;   - should handle block comments too.
27 ;;   - handle lexer modes (\header, \melodic, \lyric) etc.
28 ;;   - indentation
29 ;;   - notenames?
30 ;;   - fontlock: \melodic \melodic
31
32
33 (defconst lily-version "1.3.19"
34   "`lilypond-mode' version number.")
35
36 (defconst lily-help-address "hanwen@cs.uu.nl"
37   "Address accepting submission of bug reports.")
38
39 (defconst lily-font-lock-keywords
40   (let* ((keywords '("spanrequest" "simultaneous" "sequential" "accepts"
41                      "alternative" "bar" "breathe"
42                      "cadenza" "chordmodifiers" "chords" "clef" "cm" "consists"
43                      "consistsend" "context"
44                      "duration" "font" "grace" "header" "in" "lyrics"
45                      "key" "keysignature" "mark" "musicalpitch"
46                      "time" "times" "midi" "mm" "name" "notenames"
47                      "notes" "partial" "paper" "penalty" "property" "pt"
48                      "relative" "remove" "repeat" "repetitions" "addlyrics"
49                      "scm" "scmfile" "score" "script"
50                      "shape" "skip" "textscript" "tempo" "translator" "transpose"
51                      "type" "version" 
52                      ))
53        (kwregex (mapconcat (lambda (x) (concat "\\\\" x))  keywords "\\|")))
54
55     (list 
56       (concat ".\\(" kwregex "\\)[^a-zA-Z]")
57       (concat "^\\(" kwregex "\\)[^a-zA-Z]")
58       '(".\\(\\\\[a-zA-Z][a-zA-Z]*\\)" 1 font-lock-variable-name-face)
59       '("^[\t ]*\\([a-zA-Z][_a-zA-Z]*\\) *=" 1 font-lock-variable-name-face)     
60     ))
61   "Additional expressions to highlight in Mudela mode.")
62
63 ;; define a mode-specific abbrev table for those who use such things
64 (defvar lilypond-mode-abbrev-table nil
65   "Abbrev table in use in `lilypond-mode' buffers.")
66
67 (define-abbrev-table 'lilypond-mode-abbrev-table nil)
68
69 (defvar lilypond-mode-hook nil
70   "*Hook called by `lilypond-mode'.")
71
72 (defvar lily-mode-syntax-table nil
73   "Syntax table used in `lilypond-mode' buffers.")
74
75 ;;
76 (if lily-mode-syntax-table
77     ()
78   (setq lily-mode-syntax-table (make-syntax-table))
79   (mapcar (function
80            (lambda (x) (modify-syntax-entry
81                         (car x) (cdr x) lily-mode-syntax-table)))
82           '(( ?\( . "." ) ( ?\) . "." )
83             ( ?\[ . "." ) ( ?\] . "." )
84             ( ?\{ . "(}" ) ( ?\} . "){" )
85             ( ?\< . "(>" )( ?\> . ")>") 
86             ( ?\$ . "." ) ( ?\% . "." ) ( ?\& . "." )
87             ( ?\* . "." ) ( ?\+ . "." ) ( ?\- . "." )
88             ( ?\/ . "." )  ( ?\= . "." )
89             ( ?\| . "." ) (?\\ . "\\" )
90             ( ?\_ . "." )       
91             ( ?\' . "w")        
92             ( ?\" . "\"" )
93             ( ?\% . "<")
94             ( ?\n . ">")
95
96 ; FIXME
97 ;           ( ?%  .  ". 124b" )
98 ;           ( ?{  .  ". 23" )
99             ))
100
101   )     
102
103 (defconst lily-imenu-generic-re "^\\([a-zA-Z_][a-zA-Z0-9_]*\\) *="
104   "Regexp matching Identifier definitions.")
105
106 ;; Sadly we need this for a macro in Emacs 19.
107 (eval-when-compile
108   ;; Imenu isn't used in XEmacs, so just ignore load errors.
109   (condition-case ()
110       (require 'imenu)
111     (error nil)))
112
113 (defvar lily-imenu-generic-expression
114   (list (list nil lily-imenu-generic-re 1))
115   "Expression for imenu")
116
117
118 ;;; we're using some handy compile commands
119 (require 'compile)
120
121 (defcustom lily-command "lilypond"
122   "* LilyPond executable."
123   :type 'string
124   :group 'lily)
125
126 (defcustom lily-parameters ""
127   "*."
128   :type 'string
129   :group 'lily)
130
131 (defvar lily-regexp-alist
132   '(("\\([a-zA-Z]?:?[^:( \t\n]+\\)[:( \t]+\\([0-9]+\\)[:) \t]" 1 2))
133   "Regexp used to match LilyPond errors.  See `compilation-error-regexp-alist'.")
134
135 (defcustom lily-tex-command "tex"
136   "*."
137   :type 'string
138   :group 'lily)
139
140 (defcustom lily-xdvi-command "xdvi"
141   "*."
142   :type 'string
143   :group 'lily)
144
145 (defun lily-compile-file (command parameters file)
146   ;; Setting process-setup-function makes exit-message-function work
147   ;; even when async processes aren't supported.
148   (let ((command-args (concat command " " parameters " " file)))
149         (compile-internal command-args "No more errors" "LilyPond")))
150
151 ;; do we still need this, now that we're using compile-internal?
152 (defun lily-save-buffer ()
153   (if (buffer-modified-p) (save-buffer)))
154
155 ;;; return (dir base ext)
156 (defun split-file-name (name)
157   (let* ((i (string-match "[^/]*$" name))
158          (dir (if (> i 0) (substring name 0 i) "./"))
159          (file (substring name i (length name)))
160          (i (string-match "[^.]*$" file)))
161     (if (and
162          (> i 0)
163          (< i (length file)))
164         (list dir (substring file 0 (- i 1)) (substring file i (length file)))
165       (list dir file ""))))
166
167 ;;;###autoload
168 (defun lily-eval-buffer ()
169   "Run LilyPond on buffer."
170   (interactive)
171   (let ((buffer (buffer-name)))
172     (if (buffer-file-name)
173         (progn
174           (lily-save-buffer)
175           (lily-compile-file lily-command lily-parameters (buffer-file-name)))
176       (progn
177         (error "Buffer %s is not associated with a file" buffer)
178         (lily-eval-region (min-point) (max-point))))))
179
180 ;;;###autoload
181 (defun lily-eval-region (start end)
182   "Run LilyPond on region."
183   (interactive "r")
184   (let ((basename "emacs-lily")
185         (suffix (if (string-match "^[\\]score" (buffer-substring start end))
186                     ".ly"
187                   (if (< 50 (abs (- start end)))
188                       ".fly"
189                       ".sly"))))
190     (write-region start end (concat basename suffix) nil 'nomsg)
191     (lily-compile-file lily-command lily-parameters (concat basename suffix))))
192
193 (defun lily-running ()
194   (let ((process (get-process "lilypond")))
195   (and process
196        (eq (process-status process) 'run))))
197
198 (defun lily-tex-file (basename)
199   (call-process lily-tex-command nil t nil basename))
200
201 (defun lily-xdvi-file (basename)
202   (let ((outbuf (get-buffer-create "*lily-xdvi*"))
203         (name "xdvi")
204         (command (concat lily-xdvi-command " " basename)))
205     (if (get-process "xdvi")
206         ;; Don't open new xdvi window, but force redisplay
207         ;; We could make this an option.
208         (signal-process (process-id (get-process "xdvi")) 'SIGUSR1)
209       (if (fboundp 'start-process)
210           (let* ((process-environment (cons "EMACS=t" process-environment))
211                  (proc (start-process-shell-command name outbuf command)))
212             ;;(set-process-sentinel proc 'compilation-sentinel)
213             ;;(set-process-filter proc 'compilation-filter)
214             (set-marker (process-mark proc) (point) outbuf))
215         ;;(setq compilation-in-progress (cons proc compilation-in-progress)))
216         
217         ;; No asynchronous processes available.
218         (message "Executing `%s'..." command)
219         ;; Fake modeline display as if `start-process' were run.
220         (setq mode-line-process ":run")
221         (force-mode-line-update)
222         (sit-for 0)                     ; Force redisplay
223       (call-process shell-file-name nil outbuf nil "-c" command)
224       (message "Executing `%s'...done" command)))))
225       
226
227 ;;;###autoload
228 (defun lily-xdvi-buffer ()
229   "Run LilyPond, TeX and Xdvi on buffer."
230   (interactive)
231
232   (let* ((split (split-file-name buffer-file-name))
233          (dir (car split))
234          (base (cadr split)))
235
236     ;; we don't really need this...
237     (let ((tex (concat dir base ".tex"))
238           (dvi (concat dir base ".dvi")))
239       (if (file-exists-p tex) (delete-file tex))
240       (if (file-exists-p dvi) (delete-file dvi)))
241
242     (lily-eval-buffer)
243     (set-buffer "*lilypond*")
244     
245     ;;(setq default-directory dir)
246     (while (lily-running)
247       (continue-process (get-process "lilypond")))
248     (sit-for 0)                 ; Force redisplay
249     
250     (if (= 0 (process-exit-status (get-process "lilypond")))
251         (progn
252           (if (= 0 (lily-tex-file base))
253               (lily-xdvi-file base))))))
254   
255 ;;;###autoload
256 (defun lily-xdvi-region (start end)
257   "Run LilyPond, TeX and Xdvi on region."
258   (interactive "r")
259
260   (let ((dir default-directory)
261         (base "emacs-lily"))
262
263     ;; we don't really need this...
264     (let ((tex (concat dir base ".tex"))
265           (dvi (concat dir base ".dvi")))
266       (if (file-exists-p tex) (delete-file tex))
267       (if (file-exists-p dvi) (delete-file dvi)))
268     
269     (lily-eval-region start end)
270     (set-buffer "*lilypond*")
271     
272     ;;(setq default-directory dir)
273     (while (lily-running)
274       (continue-process (get-process "lilypond")))
275     (sit-for 0)                 ; Force redisplay
276     
277     (if (= 0 (process-exit-status (get-process "lilypond")))
278         (progn
279           (if (= 0 (lily-tex-file base))
280               (lily-xdvi-file base))))))
281
282 ;;;###autoload
283 (defun lily-kill-job ()
284   "Kill the currently running LilyPond job."
285   (interactive)
286   (quit-process (get-process "lilypond") t))
287
288 ;; hmm
289 ;;  (kill-process (get-process "xdvi") t)
290
291 (defvar lily-mode-map ()
292   "Keymap used in `lilypond-mode' buffers.")
293
294 ;; Note:  if you make changes to the map, you must do
295 ;;    M-x set-variable lily-mode-map nil
296 ;;    M-x eval-buffer
297 ;;    M-x lilypond-mode
298 ;; to let the changest take effect
299 (if lily-mode-map
300     ()
301   (setq lily-mode-map (make-sparse-keymap))
302   (define-key lily-mode-map [f9] 'lily-eval-buffer)
303   (define-key lily-mode-map [f10] 'lily-xdvi-buffer)
304   (define-key lily-mode-map [S-f9] 'lily-eval-region)
305   (define-key lily-mode-map [S-f10] 'lily-xdvi-region)
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