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.
22 ;; This is a cannabalised version of python-mode.el (HWN)
23 ;; Added lily-eval-buffer -- jcn
26 ;; * lily/ly/lilypond?
28 ;; * mail Tim Peters about silly copyright (notice)?
30 ;; - should handle block comments too.
31 ;; - handle lexer modes (\header, \melodic, \lyric) etc.
34 ;; - fontlock: \melodic \melodic
37 (defconst lily-version "1.3.19"
38 "`lilypond-mode' version number.")
40 (defconst lily-help-address "hanwen@cs.uu.nl"
41 "Address accepting submission of bug reports.")
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"
57 (kwregex (mapconcat (lambda (x) (concat "\\\\" x)) keywords "\\|")))
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)
65 "Additional expressions to highlight in Mudela mode.")
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.")
71 (define-abbrev-table 'lilypond-mode-abbrev-table nil)
73 (defvar lilypond-mode-hook nil
74 "*Hook called by `lilypond-mode'.")
76 (defvar lily-mode-syntax-table nil
77 "Syntax table used in `lilypond-mode' buffers.")
80 (if lily-mode-syntax-table
82 (setq lily-mode-syntax-table (make-syntax-table))
84 (lambda (x) (modify-syntax-entry
85 (car x) (cdr x) lily-mode-syntax-table)))
86 '(( ?\( . "." ) ( ?\) . "." )
87 ( ?\[ . "." ) ( ?\] . "." )
88 ( ?\{ . "(}" ) ( ?\} . "){" )
89 ( ?\< . "(>" )( ?\> . ")>")
90 ( ?\$ . "." ) ( ?\% . "." ) ( ?\& . "." )
91 ( ?\* . "." ) ( ?\+ . "." ) ( ?\- . "." )
92 ( ?\/ . "." ) ( ?\= . "." )
93 ( ?\| . "." ) (?\\ . "\\" )
107 (defconst lily-imenu-generic-re "^\\([a-zA-Z_][a-zA-Z0-9_]*\\) *="
108 "Regexp matching Identifier definitions.")
110 ;; Sadly we need this for a macro in Emacs 19.
112 ;; Imenu isn't used in XEmacs, so just ignore load errors.
117 (defvar lily-imenu-generic-expression
118 (list (list nil lily-imenu-generic-re 1))
119 "Expression for imenu")
122 ;;; we're using some handy compile commands
125 (defcustom lily-command "lilypond"
126 "* LilyPond executable."
130 (defcustom lily-parameters ""
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'.")
139 (defcustom lily-tex-command "tex"
144 (defcustom lily-xdvi-command "xdvi"
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")))
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)))
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)))
168 (list dir (substring file 0 (- i 1)) (substring file i (length file)))
169 (list dir file ""))))
172 (defun lily-eval-buffer ()
173 "Run LilyPond on buffer."
175 (let ((buffer (buffer-name)))
179 (lily-compile-file lily-command lily-parameters buffer))
181 (error "Buffer %s is not associated with a file" buffer)
182 (lily-eval-region (min-point) (max-point))))))
185 (defun lily-eval-region (start end)
186 "Run LilyPond on region."
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)))
195 (defun lily-running ()
196 (let ((process (get-process "lilypond")))
198 (eq (process-status process) 'run))))
200 (defun lily-tex-file (basename)
201 (call-process lily-tex-command nil t nil basename))
203 (defun lily-xdvi-file (basename)
204 (let ((outbuf (get-buffer-create "*lily-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)))
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)))))
230 (defun lily-xdvi-buffer ()
231 "Run LilyPond, TeX and Xdvi on buffer."
234 (let* ((split (split-file-name buffer-file-name))
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)))
245 (set-buffer "*lilypond*")
247 ;;(setq default-directory dir)
248 (while (lily-running)
249 (continue-process (get-process "lilypond")))
250 (sit-for 0) ; Force redisplay
252 (if (= 0 (process-exit-status (get-process "lilypond")))
254 (if (= 0 (lily-tex-file base))
255 (lily-xdvi-file base))))))
258 (defun lily-xdvi-region (start end)
259 "Run LilyPond, TeX and Xdvi on region."
262 (let ((dir default-directory)
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)))
271 (lily-eval-region start end)
272 (set-buffer "*lilypond*")
274 ;;(setq default-directory dir)
275 (while (lily-running)
276 (continue-process (get-process "lilypond")))
277 (sit-for 0) ; Force redisplay
279 (if (= 0 (process-exit-status (get-process "lilypond")))
281 (if (= 0 (lily-tex-file base))
282 (lily-xdvi-file base))))))
285 (defun lily-kill-job ()
286 "Kill the currently running LilyPond job."
288 (quit-process (get-process "lilypond") t))
291 ;; (kill-process (get-process "xdvi") t)
293 (defvar lily-mode-map ()
294 "Keymap used in `lilypond-mode' buffers.")
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)
305 ;; (global-set-key [C-f9] 'lily-eval-buffer)
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