1 ;;; lilypond-mode.el --- Major mode for editing GNU LilyPond music scores
3 ;; Copyright (C) 1992,1993,1994 Tim Peters
5 ;; Author: 1999: Jan Nieuwenhuizen
6 ;; Author: 1997: Han-Wen Nienhuys
7 ;; Author: 1995-1996 Barry A. Warsaw
8 ;; 1992-1994 Tim Peters
11 ;; Last Modified: 12SEP97
12 ;; Keywords: mudela languages music
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.
20 ;; This started out as a cannabalised version of python-mode.el, by hwn
21 ;; For changes see the LilyPond ChangeLog
24 ;; * lily/ly/lilypond?
26 ;; - should handle block comments too.
27 ;; - handle lexer modes (\header, \melodic, \lyric) etc.
30 ;; - fontlock: \melodic \melodic
33 (defconst lily-version
"1.3.19"
34 "`lilypond-mode' version number.")
36 (defconst lily-help-address
"hanwen@cs.uu.nl"
37 "Address accepting submission of bug reports.")
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"
53 (kwregex (mapconcat (lambda (x) (concat "\\\\" x
)) keywords
"\\|")))
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
)
61 "Additional expressions to highlight in Mudela mode.")
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.")
67 (define-abbrev-table 'lilypond-mode-abbrev-table nil
)
69 (defvar lilypond-mode-hook nil
70 "*Hook called by `lilypond-mode'.")
72 (defvar lily-mode-syntax-table nil
73 "Syntax table used in `lilypond-mode' buffers.")
76 (if lily-mode-syntax-table
78 (setq lily-mode-syntax-table
(make-syntax-table))
80 (lambda (x) (modify-syntax-entry
81 (car x
) (cdr x
) lily-mode-syntax-table
)))
82 '(( ?\
( .
"." ) ( ?\
) .
"." )
83 ( ?\
[ .
"." ) ( ?\
] .
"." )
84 ( ?\
{ .
"(}" ) ( ?\
} .
"){" )
85 ( ?\
< .
"(>" )( ?\
> .
")>")
86 ( ?\$ .
"." ) ( ?\% .
"." ) ( ?\
& .
"." )
87 ( ?\
* .
"." ) ( ?\
+ .
"." ) ( ?\- .
"." )
88 ( ?\
/ .
"." ) ( ?\
= .
"." )
89 ( ?\| .
"." ) (?
\\ .
"\\" )
103 (defconst lily-imenu-generic-re
"^\\([a-zA-Z_][a-zA-Z0-9_]*\\) *="
104 "Regexp matching Identifier definitions.")
106 ;; Sadly we need this for a macro in Emacs 19.
108 ;; Imenu isn't used in XEmacs, so just ignore load errors.
113 (defvar lily-imenu-generic-expression
114 (list (list nil lily-imenu-generic-re
1))
115 "Expression for imenu")
118 ;;; we're using some handy compile commands
121 (defcustom lily-command
"lilypond"
122 "* LilyPond executable."
126 (defcustom lily-parameters
""
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'.")
135 (defcustom lily-tex-command
"tex"
140 (defcustom lily-xdvi-command
"xdvi"
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")))
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)))
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
)))
164 (list dir
(substring file
0 (- i
1)) (substring file i
(length file
)))
165 (list dir file
""))))
168 (defun lily-eval-buffer ()
169 "Run LilyPond on buffer."
171 (let ((buffer (buffer-name)))
172 (if (buffer-file-name)
175 (lily-compile-file lily-command lily-parameters
(buffer-file-name)))
177 (error "Buffer %s is not associated with a file" buffer
)
178 (lily-eval-region (min-point) (max-point))))))
181 (defun lily-eval-region (start end
)
182 "Run LilyPond on region."
184 (let ((basename "emacs-lily")
185 (suffix (if (string-match "^[\\]score" (buffer-substring start end
))
187 (if (< 50 (abs (- start end
)))
190 (write-region start end
(concat basename suffix
) nil
'nomsg
)
191 (lily-compile-file lily-command lily-parameters
(concat basename suffix
))))
193 (defun lily-running ()
194 (let ((process (get-process "lilypond")))
196 (eq (process-status process
) 'run
))))
198 (defun lily-tex-file (basename)
199 (call-process lily-tex-command nil t nil basename
))
201 (defun lily-xdvi-file (basename)
202 (let ((outbuf (get-buffer-create "*lily-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)))
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
)))))
228 (defun lily-xdvi-buffer ()
229 "Run LilyPond, TeX and Xdvi on buffer."
232 (let* ((split (split-file-name buffer-file-name
))
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
)))
243 (set-buffer "*lilypond*")
245 ;;(setq default-directory dir)
246 (while (lily-running)
247 (continue-process (get-process "lilypond")))
248 (sit-for 0) ; Force redisplay
250 (if (= 0 (process-exit-status (get-process "lilypond")))
252 (if (= 0 (lily-tex-file base
))
253 (lily-xdvi-file base
))))))
256 (defun lily-xdvi-region (start end
)
257 "Run LilyPond, TeX and Xdvi on region."
260 (let ((dir default-directory
)
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
)))
269 (lily-eval-region start end
)
270 (set-buffer "*lilypond*")
272 ;;(setq default-directory dir)
273 (while (lily-running)
274 (continue-process (get-process "lilypond")))
275 (sit-for 0) ; Force redisplay
277 (if (= 0 (process-exit-status (get-process "lilypond")))
279 (if (= 0 (lily-tex-file base
))
280 (lily-xdvi-file base
))))))
283 (defun lily-kill-job ()
284 "Kill the currently running LilyPond job."
286 (quit-process (get-process "lilypond") t
))
289 ;; (kill-process (get-process "xdvi") t)
291 (defvar lily-mode-map
()
292 "Keymap used in `lilypond-mode' buffers.")
294 ;; Note: if you make changes to the map, you must do
295 ;; M-x set-variable lily-mode-map nil
298 ;; to let the changest take effect
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)
308 (defun lilypond-mode ()
309 "Major mode for editing Mudela files."
311 ;; set up local variables
312 (kill-all-local-variables)
314 (make-local-variable 'font-lock-defaults)
315 (setq font-lock-defaults '(lily-font-lock-keywords))
317 (make-local-variable 'paragraph-separate)
318 (setq paragraph-separate "^[ \t]*$")
320 (make-local-variable 'paragraph-start)
321 (setq paragraph-start "^[ \t]*$")
323 (make-local-variable 'comment-start)
324 (setq comment-start "%")
326 (make-local-variable 'comment-start-skip)
327 (setq comment-start-skip "%{? *")
329 (make-local-variable 'comment-end)
330 (setq comment-end "\n")
332 (make-local-variable 'block-comment-start)
333 (setq block-comment-start "%{")
335 (make-local-variable 'block-comment-end)
336 (setq block-comment-end "%}")
338 ;; (make-local-variable 'comment-column)
339 ;; (setq comment-column 40)
341 (make-local-variable 'imenu-generic-expression)
342 (setq imenu-generic-expression lily-imenu-generic-expression)
344 (make-local-variable 'indent-line-function)
345 (setq indent-line-function 'indent-relative-maybe)
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)
354 ;; run the mode hook. lily-mode-hook use is deprecated
355 (run-hooks 'lilypond-mode-hook))
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)))
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)))
373 (defun lily-version ()
374 "Echo the current version of `lilypond-mode' in the minibuffer."
376 (message "Using `lilypond-mode' version %s" lily-version)
377 (lily-keep-region-active))
379 (provide 'lilypond-mode)
380 ;;; lilypond-mode.el ends here