lilypond-1.3.24
[lilypond.git] / lilypond-mode.el
blob8a4c7ccf6fa0c5419c6403b68fd8e3e67ff073ad
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
9 ;; Created: Feb 1992
10 ;; Version: 0.0
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
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
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"
51 "type" "version"
53 (kwregex (mapconcat (lambda (x) (concat "\\\\" x)) keywords "\\|")))
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)
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))
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 . ">")
96 ; FIXME
97 ; ( ?% . ". 124b" )
98 ; ( ?{ . ". 23" )
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.
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)))
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
119 (require 'compile)
121 (defcustom lily-command "lilypond"
122 "* LilyPond executable."
123 :type 'string
124 :group 'lily)
126 (defcustom lily-parameters ""
127 "*."
128 :type 'string
129 :group 'lily)
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"
136 "*."
137 :type 'string
138 :group 'lily)
140 (defcustom lily-xdvi-command "xdvi"
141 "*."
142 :type 'string
143 :group 'lily)
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)))
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 ""))))
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))))))
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))))
193 (defun lily-running ()
194 (let ((process (get-process "lilypond")))
195 (and process
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*"))
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)))
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)))))
227 ;;;###autoload
228 (defun lily-xdvi-buffer ()
229 "Run LilyPond, TeX and Xdvi on buffer."
230 (interactive)
232 (let* ((split (split-file-name buffer-file-name))
233 (dir (car split))
234 (base (cadr split)))
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)))
242 (lily-eval-buffer)
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")))
251 (progn
252 (if (= 0 (lily-tex-file base))
253 (lily-xdvi-file base))))))
255 ;;;###autoload
256 (defun lily-xdvi-region (start end)
257 "Run LilyPond, TeX and Xdvi on region."
258 (interactive "r")
260 (let ((dir default-directory)
261 (base "emacs-lily"))
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")))
278 (progn
279 (if (= 0 (lily-tex-file base))
280 (lily-xdvi-file base))))))
282 ;;;###autoload
283 (defun lily-kill-job ()
284 "Kill the currently running LilyPond job."
285 (interactive)
286 (quit-process (get-process "lilypond") t))
288 ;; hmm
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
296 ;; M-x eval-buffer
297 ;; M-x lilypond-mode
298 ;; to let the changest take effect
299 (if lily-mode-map
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."
310 (interactive)
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."
375 (interactive)
376 (message "Using `lilypond-mode' version %s" lily-version)
377 (lily-keep-region-active))
379 (provide 'lilypond-mode)
380 ;;; lilypond-mode.el ends here