Initial Commit
[packages] / xemacs-packages / ess / lisp / noweb-font-lock-mode.el
1 ;; noweb-font-lock-mode.el - edit noweb files with GNU Emacs
2
3 ;; Copyright (C) 1999 by  Adnan Yaqub (AYaqub@orga.com)
4 ;;                    and Mark Lunt (mark.lunt@mrc-bsu.cam.ac.uk
5 ;; Copyright (C) 2002 by A.J. Rossini <rossini@u.washington.edu>
6 ;; Copyright (C) 2003--2004 A.J. Rossini, Rich M. Heiberger, Martin
7 ;;      Maechler, Kurt Hornik, Rodney Sparapani, and Stephen Eglen.
8
9 ;; Maintainers: ESS-core <ESS-core@stat.math.ethz.ch>
10
11 ;; This program is free software; you can redistribute it and/or modify
12 ;; it under the terms of the GNU General Public License as published by
13 ;; the Free Software Foundation; either version 2, or (at your option)
14 ;; any later version.
15 ;; 
16 ;; This program is distributed in the hope that it will be useful,
17 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
18 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19 ;; GNU General Public License for more details.
20 ;; 
21 ;; You should have received a copy of the GNU General Public License
22 ;; along with this program; if not, write to the Free Software
23 ;; Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
24 ;; 
25 ;; 
26 ;;; Code-dependent highlighting
27
28 ;;  *****
29 ;;  
30 ;;  Adding highlighting to noweb-mode.el
31 ;;  
32 ;;  Here is a description of how one can add highlighting via the
33 ;;  font-lock package to noweb buffers.  It uses the hooks provided by
34 ;;  noweb-mode.el.  The solution provides the following features:
35 ;;  1) The documentation chunks are highlighted in the noweb-doc-mode
36 ;;  (e.g., LaTeX).
37 ;;  2) The code chunks without mode comments (-*- mode -*-) are
38 ;;  highlighted in the noweb-code-mode.
39 ;;  3) The code chunks with mode comments (-*- mode -*-) on the first
40 ;;  line of the first chunk with this name are highlighted in the mode
41 ;;  in the comment.
42 ;;  
43 ;;  For example, given the file:
44 ;;  
45 ;;    % -*- mode: Noweb; noweb-code-mode: c-mode -*-
46 ;;  
47 ;;    \begin{itemize}
48 ;;    \item a main routine written in C,
49 ;;    \item a log configuration file parser written in YACC, and
50 ;;    \item a lexical analyzer written in Lex.
51 ;;    \end{itemize}
52 ;;  
53 ;;    <<warning c comment>>=
54 ;;    /* DO NOT EDIT ME! */
55 ;;    /* This file was automatically generated from %W% (%G%). */
56 ;;    @
57 ;;  
58 ;;    <<warning nroff comment>>=
59 ;;    .\" -*- nroff -*-
60 ;;    .\" DO NOT EDIT ME!
61 ;;    .\" This file was automatically generated from %W% (%G%).
62 ;;    @
63 ;;  
64 ;;  The LaTeX list is highlighted in latex-mode (the default noweb doc
65 ;;  mode), the chunk <<warning c comment>> is highlighted in c-mode (the
66 ;;  default noweb code mode), and the chunk <<warning nroff comment>> is
67 ;;  highlighted in nroff-mode due to the "-*- nroff -*-" comment.
68 ;;  
69 ;;  Chunks are highlighted each time point moves into them from a
70 ;;  different mode. They are also fontified 'on the fly', but this is
71 ;;  less reliable, since the syntax can depend on the context. It's as
72 ;;  good as you would get outside noweb-mode, though.
73 ;;  
74 ;;  To use it, you must add 
75 ;;  (require noweb-font-lock-mode) to your .emacs file. 
76 ;;  Then, if you use either global-font-lock or turn-on-font-lock
77 ;;  statements, any noweb-mode buffers will be fontified
78 ;;  appropriately. (We have to redefine turn-on-font-lock, but it
79 ;;  saves breaking other packages (in particular ESS, which I use a
80 ;;  lot), that assume that turn-on-font-lock is the way to turn on
81 ;;  font locking.
82
83 ;;  Alternatively, you can turn noweb-font-lock-mode on and off by
84 ;;  using M-x noweb-font-lock-mode. However, turning
85 ;;  noweb-font-lock-mode off when global-font-lock-mode is t makes it
86 ;;  impossible to use font-locking in that buffer subsequently, other
87 ;;  than by turning noweb-font-lock-mode back on.
88
89 ;;  2) The highlighting sometimes get confused, but this is no longer
90 ;;  a noweb problem. Highlighting should work as well within a chunk
91 ;;  as it does without noweb-mode.  
92 ;;  There are some problems with, for example latex-mode: a `$' in a
93 ;;  verbatim environment with throw the font-locking out.
94 ;;  One slight blemish is that code-quotes are highlighted as comments
95 ;;  as they are being entered. They are only highlighted correctly
96 ;;  after `noweb-font-lock-fontify-chunk' has been run, either as a
97 ;;  command or through changing to a different chunk and back again
98 ;;  (unless they lie on a single line, in which case they are
99 ;;  fontified correctly once they are completed).
100
101 (require 'noweb-mode)
102 (require 'font-lock)
103
104 (defvar noweb-font-lock-mode nil
105   "Buffer local variable, t iff this buffer is using noweb-font-lock-mode.")
106
107 (defvar noweb-use-font-lock-mode t
108   "DO NOT CHANGE THIS VARIABLE 
109 If you use nw-turn-on-font-lock to turn on font-locking, then turn it
110 off again, it would come back on again of its own accord when you
111 changed major-mode. This variable is used internally to stop it.")
112
113 (defvar noweb-font-lock-mode-hook nil
114   "Hook that is run after entering noweb-font-lock mode.")
115
116 (defvar noweb-font-lock-max-initial-chunks 2
117   "Maximum number of chunks to fontify initially.
118 If nil, will fontify the entire buffer when
119 noweb-font-lock-initial-fontify-buffer is called" )
120
121 (defvar old-beginning-of-syntax nil
122   "Stores the function used to find the beginning of syntax in the
123 current major mode. noweb-font-lock-mode needs a different one." )
124
125 ;; (AJR) the next two lines were originally font-lock-warning-face
126 ;; methods; XEmacs 20.4 doesn't define this, sigh...  -- KLUDGE --.
127
128 (defvar noweb-font-lock-doc-start-face font-lock-reference-face
129   "Face to use to highlight the `@' at the start of each doc chunk")
130
131 (defvar noweb-font-lock-brackets-face font-lock-reference-face
132   "Face to use to highlight `<<', `>>' `[[' and `]]' ")
133
134 (defvar noweb-font-lock-chunk-name-face font-lock-keyword-face
135   "Face to use to highlight the between `<<' and `>>'")
136
137 (defvar noweb-font-lock-code-quote-face font-lock-keyword-face
138   "Face to use to highlight the between `[[' and `]]'")
139
140 ;; Now we add [[noweb-font-lock-mode]] to the list of existing minor
141 ;; modes. The string ``NWFL'' will be added to the mode-line: ugly, but
142 ;; brief.
143
144 (if (not (assq 'noweb-font-lock-mode minor-mode-alist))
145     (setq minor-mode-alist (append minor-mode-alist
146                                    (list '(noweb-font-lock-mode " NWFL")))))
147
148 ;; An ugly kludge to get around problems with global-font-lock, which
149 ;; fontifies the entire buffer in the new major mode every time you 
150 ;; change mode, which is time-consuming and makes a pigs trotters of 
151 ;; it. Trying to stop it looks tricky, but using this function as your
152 ;; `font-lock-fontify-buffer' function stops it wasting your time
153
154 (defun nwfl-donowt()
155   "This function does nothing at all")
156
157 ;; The following function is just a wrapper for noweb-font-lock-mode,
158 ;; enabling it to be called as noweb-font-lock-minor-mode instead.
159
160 (defun noweb-font-lock-minor-mode ( &optional arg)
161   "Minor meta mode for managing syntax highlighting in noweb files. 
162 See NOWEB-FONT-LOCK-MODE."
163   (interactive)
164   (noweb-font-lock-mode arg))
165
166 ;; Here we get to the meat of the problem
167
168 (defun noweb-font-lock-mode ( &optional arg)
169 "Minor mode for syntax highlighting when using noweb-mode to edit noweb files.
170 Each chunk is fontified in accordance with its own mode"
171   (interactive "P")
172   (if (or noweb-mode noweb-font-lock-mode)
173       (progn
174 ; This bit is tricky: copied almost verbatim from bib-cite-mode.el
175 ; It seems to ensure that the variable noweb-font-lock-mode is made
176 ; local to this buffer. It then sets noweb-font-lock-mode to `t' if 
177 ;     1) It was called with a prefix argument greater than 0
178 ; or  2) It was called with no argument, and noweb-font-lock-mode is
179 ;        currently nil
180 ; noweb-font-lock-mode is nil if the prefix argument was <= 0 or there
181 ; was no prefix argument and noweb-font-lock-mode is currently `t'
182       (set (make-local-variable 'noweb-font-lock-mode)
183            (if arg
184                (> (prefix-numeric-value arg) 0)
185              (not noweb-font-lock-mode)))
186     ;; Now, if noweb-font-lock-mode is true, we want to turn
187     ;; noweb-font-lock-mode on
188     (cond 
189      (noweb-font-lock-mode                 ;Setup the minor-mode
190       (progn
191         (if (and (boundp 'global-font-lock-mode) global-font-lock-mode)
192             (progn
193               (mapcar 'noweb-make-variable-permanent-local
194                       '(font-lock-fontify-buffer-function
195                         font-lock-unfontify-buffer-function))
196               (setq font-lock-fontify-buffer-function 'nwfl-donowt)
197               (setq font-lock-unfontify-buffer-function 'nwfl-donowt)))
198         (mapcar 'noweb-make-variable-permanent-local
199                 '(noweb-font-lock-mode
200                   font-lock-beginning-of-syntax-function
201                   noweb-use-font-lock-mode
202                   after-change-functions))
203         (setq noweb-font-lock-mode t)
204         (make-local-hook 'after-change-functions)
205         (add-hook 'after-change-functions 
206                   'font-lock-after-change-function nil t)
207         (add-hook 'noweb-font-lock-mode-hook 'noweb-font-lock-mode-fn)
208         (add-hook 'noweb-changed-chunk-hook 
209                   'noweb-font-lock-fontify-this-chunk)
210         (run-hooks 'noweb-font-lock-mode-hook)  
211         (message "noweb-font-lock mode: use `M-x noweb-font-lock-describe-mode' for more info")))
212      ;; If we didn't do the above, then we want to turn noweb-font-lock-mode
213      ;; off, no matter what (hence the condition `t')
214     (t
215      (progn
216        (if (and (boundp 'global-font-lock-mode) global-font-lock-mode)
217            (progn
218              ;; (setq font-lock-fontify-buffer-function
219              ;;       'font-lock-default-fontify-buffer) 
220              ;; Get back our unfontify buffer function
221              (setq font-lock-unfontify-buffer-function
222                    'font-lock-default-unfontify-buffer)))
223        (remove-hook 'noweb-font-lock-mode-hook 'noweb-font-lock-mode-fn)
224        (remove-hook 'noweb-changed-chunk-hook 
225                     'noweb-font-lock-fontify-this-chunk)
226        (remove-hook 'after-change-functions 
227                     'font-lock-after-change-function )
228        (font-lock-default-unfontify-buffer)
229        (setq noweb-use-font-lock-mode nil)
230        (message "noweb-font-lock-mode removed")))))
231     (message "noweb-font-lock-mode can only be used with noweb-mode")))
232
233 (defun noweb-start-of-syntax ()
234   "Go to the place to start fontifying from"
235   (interactive)
236   (goto-char (car (noweb-chunk-region))))
237
238 (defun noweb-font-lock-fontify-chunk-by-number ( chunk-num )
239   "Fontify chunk chunk-num based on the current major mode."
240   (save-excursion 
241     (font-lock-set-defaults)
242     (setq old-beginning-of-syntax font-lock-beginning-of-syntax-function)
243     (setq font-lock-beginning-of-syntax-function 'noweb-start-of-syntax)
244     (setq font-lock-keywords 
245 ;         (append font-lock-keywords
246 ;                 '(("\\(\\[\\[\\)\\([^]]*\\]*\\)\\(\\]\\]\\|\\$\\)" 
247 ;                    (1 noweb-font-lock-brackets-face prepend )
248 ;                    (2 noweb-font-lock-code-quote-face prepend)
249 ;                    (3 noweb-font-lock-brackets-face prepend))
250 ;                   ("^[ \t\n]*\\(<<\\)\\([^>]*\\)\\(>>=?\\)"
251 ;                    (1 noweb-font-lock-brackets-face  prepend )
252 ;                    (2 noweb-font-lock-chunk-name-face prepend)
253 ;                    (3 noweb-font-lock-brackets-face prepend))
254 ;                   ("^@[ \t\n]+" 
255 ;                    (0 noweb-font-lock-doc-start-face prepend )))))
256           (append font-lock-keywords
257                   '(("\\(\\[\\[\\)\\([^]]*\\]*\\)\\(\\]\\]\\|\\$\\)" 
258                      (1 font-lock-reference-face prepend )
259                      (2 font-lock-keyword-face prepend)
260                      (3 font-lock-reference-face prepend))
261                     ("^[ \t\n]*\\(<<\\)\\([^>]*\\)\\(>>=?\\)"
262                      (1 font-lock-reference-face  prepend )
263                      (2 font-lock-keyword-face prepend)
264                      (3 font-lock-reference-face prepend))
265                     ("^@[ \t\n]+" 
266                      (0 font-lock-reference-face prepend )))))
267
268
269     (let ((r (cons (marker-position (cdr (aref noweb-chunk-vector 
270                                                chunk-num)))
271                    (marker-position (cdr (aref noweb-chunk-vector 
272                                                (1+ chunk-num)))))))
273       (font-lock-fontify-region (car r) (cdr r))
274       t)))
275
276 (defun noweb-font-lock-fontify-this-chunk ()
277   "Fontify this chunk according to its own major mode. 
278 Since we are in the chunk, the major mode will already have been set
279 by noweb-mode.el"
280   (interactive)
281   (noweb-font-lock-fontify-chunk-by-number (noweb-find-chunk-index-buffer)))
282
283 (defun noweb-font-lock-initial-fontify-buffer ()
284   "Applies syntax highlighting to some or all chunks in a noweb buffer.
285 The number of chunks is set by noweb-font-lock-max-initial-chunks: if
286 this is nil, the entire buffer is fontified.
287 It is intended to be called when first entering noweb-font-lock-mode.
288 For other purposes, use noweb-font-lock-fontify-chunks."
289   (interactive)
290 ;; This will be tricky. It will be very slow to go throught the chunks
291 ;; in order, switching major modes all the time.
292 ;; So, we will do the documentation in one pass, the code in a second
293 ;; pass. This could still be a little slow if we have to swap between
294 ;; different code modes regularly, but it should be bearable. It should
295 ;; only happen when the file is first read in, anyway
296   (save-excursion 
297   (let (start-chunk end-chunk this-chunk chunk-counter)
298     (setq this-chunk (noweb-find-chunk-index-buffer))
299     (if noweb-font-lock-max-initial-chunks
300         (progn
301           (setq start-chunk 
302                 (max 0 
303                      (- this-chunk 
304                         (/ noweb-font-lock-max-initial-chunks 2))))
305 ;; Don't you just love hairy lisp syntax ? The above means set the
306 ;; starting chunk to the current chunk minus half of
307 ;; noweb-font-lock-max-initial-chunks, unless that is negative in
308 ;; which case set it to 0
309           (setq end-chunk (+ start-chunk noweb-font-lock-max-initial-chunks))
310           (if (> end-chunk (- (length noweb-chunk-vector) 2))
311               (setq end-chunk (- (length noweb-chunk-vector) 2))))
312 ;; If noweb-font-lock-max-initial-chunks is nil, do the whole buffer
313       (progn 
314         (setq start-chunk 0)
315         (setq end-chunk (- (length noweb-chunk-vector) 2))))
316     (noweb-font-lock-fontify-chunks start-chunk end-chunk))))
317
318 (defun noweb-font-lock-fontify-buffer ()
319   "This function will fontify each chunk in the buffer appropriately."
320   (interactive)
321   (let ((start-chunk 0)
322         (end-chunk (- (length noweb-chunk-vector) 2)))
323     (noweb-font-lock-fontify-chunks start-chunk end-chunk)))
324
325 (defun noweb-font-lock-fontify-chunks (start-chunk end-chunk)
326   "Fontify a noweb file from start-chunk to end-chunk"
327   (interactive)
328   (let (chunk-counter)
329     (save-excursion
330       (message "Fontifying from %d to %d" start-chunk end-chunk)
331       ;; Want to set DOC mode for the first Doc chunk, not for the others
332       (setq chunk-counter start-chunk)
333       (while  (stringp (car (aref noweb-chunk-vector chunk-counter)))
334         (setq chunk-counter (+ chunk-counter 1)))
335       (goto-char (cdr (aref noweb-chunk-vector chunk-counter)))
336       (noweb-select-mode)
337       ;; Now go through the chunks, fontifying the documentation ones.
338       (while (<= chunk-counter end-chunk)
339         (if  (not (stringp (car (aref noweb-chunk-vector chunk-counter))))
340             (noweb-font-lock-fontify-chunk-by-number chunk-counter))
341         (message "Fontifying documentation chunks: chunk %d" chunk-counter)
342         (setq chunk-counter (+ 1 chunk-counter))) 
343       ;; Go back to the start and go through the chunks, fontifying the code ones.
344       (setq chunk-counter start-chunk)  
345       (message "About to do code chunks")
346       (while (<= chunk-counter end-chunk)
347         (if (stringp (car (aref noweb-chunk-vector chunk-counter)))
348             (progn
349               ;; It's a code chunk: goto it to set the correct code mode, then
350               ;; fontify it.
351               (message "Fontifying code chunks: chunk %d" chunk-counter)
352               (goto-char (cdr (aref noweb-chunk-vector chunk-counter)))
353               (noweb-select-mode)
354               (noweb-font-lock-fontify-this-chunk)))
355         (setq chunk-counter (1+ chunk-counter))))
356     (noweb-select-mode)))
357
358 (defun noweb-font-lock-mode-fn()
359   "Function that is intended to be attached to noweb-font-lock-mode-hook."
360   (noweb-font-lock-initial-fontify-buffer))
361
362 ;; This is a wee bit of a hack. If people attach `turn-on-font-lock'
363 ;; to their major mode hook, it will play hell with
364 ;; noweb-font-lock-mode. I had hoped that providing a replacement
365 ;; `nw-turn-on-font-lock' would solve the problem, but it didn't
366 ;; (sometimes turn-on-font-lock appears in places other than
367 ;; `.emacs', such as in ESS). So rather than have it fall over if 
368 ;; turn-on-lock was around, I redefined turn-on-font-lock to do the 
369 ;; right thing.
370
371 (defvar noweb-old-turn-on-font-lock nil)
372
373 (defun nw-turn-on-font-lock ()
374   "Turn on font-lock mode, with due regard to whether we are in noweb-mode"
375   (if (not noweb-mode)
376       (noweb-old-turn-on-font-lock)
377     (if (and (not noweb-font-lock-mode) noweb-use-font-lock-mode)
378         (noweb-font-lock-mode ))))
379
380 (if (functionp 'noweb-old-turn-on-font-lock)
381     nil
382   (progn
383     (fset 'noweb-old-turn-on-font-lock (symbol-function 'turn-on-font-lock))
384     (fset 'turn-on-font-lock (symbol-function 'nw-turn-on-font-lock))))
385
386 (provide 'noweb-font-lock-mode)
387 ;;  *****
388 ;;  
389 ;;  Adnan Yaqub (AYaqub@orga.com)
390 ;;  ORGA Kartensysteme GmbH // An der Kapelle 2 // D-33104 Paderborn // Germany
391 ;;  Tel. +49 5254 991-823 //Fax. +49 5254 991-749
392
393
394 \f
395 ;; Local Variables:
396 ;; mode:emacs-lisp
397 ;; End:
398