;;; ess-noweb-font-lock-mode.el --- edit noweb files with GNU Emacs12;; Copyright (C) 1999 by Adnan Yaqub ([email protected])3;; and Mark Lunt ([email protected]4;; Copyright (C) 2002 by A.J. Rossini <[email protected]>5;; Copyright (C) 2003--2004 A.J. Rossini, Richard M. Heiberger, Martin6;; Maechler, Kurt Hornik, Rodney Sparapani, and Stephen Eglen.78;; Maintainer: ESS-core <[email protected]>910;; This program is free software; you can redistribute it and/or modify11;; it under the terms of the GNU General Public License as published by12;; the Free Software Foundation; either version 2, or (at your option)13;; any later version.14;;15;; This program is distributed in the hope that it will be useful,16;; but WITHOUT ANY WARRANTY; without even the implied warranty of17;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the18;; GNU General Public License for more details.19;;20;; A copy of the GNU General Public License is available at21;; http://www.r-project.org/Licenses/22;;2324;;; Commentary:2526;; Code-dependent highlighting27;; *****28;;29;; Adding highlighting to ess-noweb-mode.el30;;31;; Here is a description of how one can add highlighting via the32;; font-lock package to noweb buffers. It uses the hooks provided by33;; ess-noweb-mode.el. The solution provides the following features:34;; 1) The documentation chunks are highlighted in the ess-noweb-doc-mode35;; (e.g., LaTeX).36;; 2) The code chunks without mode comments (-*- mode -*-) are37;; highlighted in the ess-noweb-code-mode.38;; 3) The code chunks with mode comments (-*- mode -*-) on the first39;; line of the first chunk with this name are highlighted in the mode40;; in the comment.41;;42;; For example, given the file:43;;44;; % -*- mode: Noweb; ess-noweb-code-mode: c-mode -*-45;;46;; \begin{itemize}47;; \item a main routine written in C,48;; \item a log configuration file parser written in YACC, and49;; \item a lexical analyzer written in Lex.50;; \end{itemize}51;;52;; <<warning c comment>>=53;; /* DO NOT EDIT ME! */54;; /* This file was automatically generated from %W% (%G%). */55;; @56;;57;; <<warning nroff comment>>=58;; .\" -*- nroff -*-59;; .\" DO NOT EDIT ME!60;; .\" This file was automatically generated from %W% (%G%).61;; @62;;63;; The LaTeX list is highlighted in latex-mode (the default noweb doc64;; mode), the chunk <<warning c comment>> is highlighted in c-mode (the65;; default noweb code mode), and the chunk <<warning nroff comment>> is66;; highlighted in nroff-mode due to the "-*- nroff -*-" comment.67;;68;; Chunks are highlighted each time point moves into them from a69;; different mode. They are also fontified 'on the fly', but this is70;; less reliable, since the syntax can depend on the context. It's as71;; good as you would get outside ess-noweb-mode, though.72;;73;; To use it, you must add74;; (require 'ess-noweb-font-lock-mode) to your .emacs file.75;; Then, if you use either global-font-lock or turn-on-font-lock76;; statements, any ess-noweb-mode buffers will be fontified77;; appropriately. (We have to redefine turn-on-font-lock, but it78;; saves breaking other packages (in particular ESS, which I use a79;; lot), that assume that turn-on-font-lock is the way to turn on80;; font locking.8182;; Alternatively, you can turn ess-noweb-font-lock-mode on and off by83;; using M-x ess-noweb-font-lock-mode. However, turning84;; ess-noweb-font-lock-mode off when global-font-lock-mode is t makes it85;; impossible to use font-locking in that buffer subsequently, other86;; than by turning ess-noweb-font-lock-mode back on.8788;; 2) The highlighting sometimes get confused, but this is no longer89;; a noweb problem. Highlighting should work as well within a chunk90;; as it does without ess-noweb-mode.91;; There are some problems with, for example latex-mode: a `$' in a92;; verbatim environment with throw the font-locking out.93;; One slight blemish is that code-quotes are highlighted as comments94;; as they are being entered. They are only highlighted correctly95;; after `ess-noweb-font-lock-fontify-chunk' has been run, either as a96;; command or through changing to a different chunk and back again97;; (unless they lie on a single line, in which case they are98;; fontified correctly once they are completed).99100;;; Code:101102(require 'ess-noweb-mode)103(require 'font-lock)104105(defvar ess-noweb-font-lock-mode nil106"Buffer local variable, t iff this buffer is using ess-noweb-font-lock-mode.")107108(defvar ess-noweb-use-font-lock-mode t109"DO NOT CHANGE THIS VARIABLE110If you use nw-turn-on-font-lock to turn on font-locking, then turn it111off again, it would come back on again of its own accord when you112changed major-mode. This variable is used internally to stop it.")113114(defvar ess-noweb-font-lock-mode-hook nil115"Hook that is run after entering ess-noweb-font-lock mode.")116117(defvar ess-noweb-font-lock-max-initial-chunks 30118"Maximum number of chunks to fontify initially.119If nil, will fontify the entire buffer when120ess-noweb-font-lock-initial-fontify-buffer is called" )121122(defvar old-beginning-of-syntax nil123"Stores the function used to find the beginning of syntax in the124current major mode. ess-noweb-font-lock-mode needs a different one." )125126;; (AJR) the next two lines were originally font-lock-warning-face127;; methods; XEmacs 20.4 doesn't define this, sigh... -- KLUDGE --.128129(defvar ess-noweb-font-lock-doc-start-face font-lock-reference-face130"Face to use to highlight the `@' at the start of each doc chunk")131132(defvar ess-noweb-font-lock-brackets-face font-lock-reference-face133"Face to use to highlight `<<', `>>' `[[' and `]]' ")134135(defvar ess-noweb-font-lock-chunk-name-face font-lock-keyword-face136"Face to use to highlight the between `<<' and `>>'")137138(defvar ess-noweb-font-lock-code-quote-face font-lock-keyword-face139"Face to use to highlight the between `[[' and `]]'")140141;; Now we add [[ess-noweb-font-lock-mode]] to the list of existing minor142;; modes. The string ``NWFL'' will be added to the mode-line: ugly, but143;; brief.144145(if (not (assq 'ess-noweb-font-lock-mode minor-mode-alist))146(setq minor-mode-alist (append minor-mode-alist147(list '(ess-noweb-font-lock-mode " NWFL")))))148149;; An ugly kludge to get around problems with global-font-lock, which150;; fontifies the entire buffer in the new major mode every time you151;; change mode, which is time-consuming and makes a pigs trotters of152;; it. Trying to stop it looks tricky, but using this function as your153;; `font-lock-fontify-buffer' function stops it wasting your time154155(defun nwfl-donowt()156"This function does nothing at all")157158;; The following function is just a wrapper for ess-noweb-font-lock-mode,159;; enabling it to be called as ess-noweb-font-lock-minor-mode instead.160161(defun ess-noweb-font-lock-minor-mode ( &optional arg)162"Minor meta mode for managing syntax highlighting in noweb files.163See ess-noweb-font-lock-mode."164(interactive)165(ess-noweb-font-lock-mode arg))166167;; Here we get to the meat of the problem168169(defun ess-noweb-font-lock-mode ( &optional arg)170"Minor mode for syntax highlighting when using ess-noweb-mode to edit noweb files.171Each chunk is fontified in accordance with its own mode"172(interactive "P")173(if (or ess-noweb-mode ess-noweb-font-lock-mode)174(progn175;; This bit is tricky: copied almost verbatim from bib-cite-mode.el176;; It seems to ensure that the variable ess-noweb-font-lock-mode is made177;; local to this buffer. It then sets ess-noweb-font-lock-mode to `t' if178;; 1) It was called with a prefix argument greater than 0179;; or 2) It was called with no argument, and ess-noweb-font-lock-mode is180;; currently nil181;; ess-noweb-font-lock-mode is nil if the prefix argument was <= 0 or there182;; was no prefix argument and ess-noweb-font-lock-mode is currently `t'183(set (make-local-variable 'ess-noweb-font-lock-mode)184(if arg185(> (prefix-numeric-value arg) 0)186(not ess-noweb-font-lock-mode)))187;; Now, if ess-noweb-font-lock-mode is true, we want to turn188;; ess-noweb-font-lock-mode on189(cond190(ess-noweb-font-lock-mode ;Setup the minor-mode191(when (and (boundp 'global-font-lock-mode) global-font-lock-mode)192(mapc 'ess-noweb-make-variable-permanent-local193'(font-lock-fontify-buffer-function194font-lock-unfontify-buffer-function))195(setq font-lock-fontify-buffer-function 'nwfl-donowt)196(setq font-lock-unfontify-buffer-function 'nwfl-donowt))197(mapcar 'ess-noweb-make-variable-permanent-local198'(ess-noweb-font-lock-mode199font-lock-dont-widen200font-lock-beginning-of-syntax-function201syntax-begin-function202ess-noweb-use-font-lock-mode203after-change-functions))204(setq ess-noweb-font-lock-mode t205font-lock-dont-widen t)206(when (< emacs-major-version 21) ; needed for emacs < 21.1 only :207(make-local-hook 'after-change-functions))208(add-hook 'after-change-functions209'font-lock-after-change-function nil t)210(add-hook 'ess-noweb-font-lock-mode-hook 'ess-noweb-font-lock-mode-fn)211(add-hook 'ess-noweb-changed-chunk-hook212'ess-noweb-font-lock-fontify-this-chunk)213(run-hooks 'ess-noweb-font-lock-mode-hook)214(message "ess-noweb-font-lock mode: use `M-x ess-noweb-font-lock-describe-mode' for more info"))215;; If we didn't do the above, then we want to turn ess-noweb-font-lock-mode216;; off, no matter what (hence the condition `t')217(t218(when (and (boundp 'global-font-lock-mode) global-font-lock-mode)219;; (setq font-lock-fontify-buffer-function220;; 'font-lock-default-fontify-buffer)221;; Get back our unfontify buffer function222(setq font-lock-unfontify-buffer-function223'font-lock-default-unfontify-buffer))224(remove-hook 'ess-noweb-font-lock-mode-hook 'ess-noweb-font-lock-mode-fn)225(remove-hook 'ess-noweb-changed-chunk-hook226'ess-noweb-font-lock-fontify-this-chunk)227(remove-hook 'after-change-functions228'font-lock-after-change-function )229(font-lock-default-unfontify-buffer)230(setq ess-noweb-use-font-lock-mode nil)231(message "ess-noweb-font-lock-mode removed"))))232(message "ess-noweb-font-lock-mode can only be used with ess-noweb-mode")))233234(defun ess-noweb-start-of-syntax ()235"Go to the place to start fontifying from"236(interactive)237(goto-char (car (ess-noweb-chunk-region))))238239(defun ess-noweb-font-lock-fontify-chunk-by-number ( chunk-num )240"Fontify chunk chunk-num based on the current major mode."241(save-excursion242(font-lock-set-defaults)243(setq old-beginning-of-syntax font-lock-beginning-of-syntax-function)244(setq font-lock-beginning-of-syntax-function 'ess-noweb-start-of-syntax)245(setq syntax-begin-function 'ess-noweb-start-of-syntax)246(setq font-lock-keywords247;; (append font-lock-keywords248;; '(("\\(\\[\\[\\)\\([^]]*\\]*\\)\\(\\]\\]\\|\\$\\)"249;; (1 ess-noweb-font-lock-brackets-face prepend )250;; (2 ess-noweb-font-lock-code-quote-face prepend)251;; (3 ess-noweb-font-lock-brackets-face prepend))252;; ("^[ \t\n]*\\(<<\\)\\([^>]*\\)\\(>>=?\\)"253;; (1 ess-noweb-font-lock-brackets-face prepend )254;; (2 ess-noweb-font-lock-chunk-name-face prepend)255;; (3 ess-noweb-font-lock-brackets-face prepend))256;; ("^@[ \t\n]+"257;; (0 ess-noweb-font-lock-doc-start-face prepend )))))258(append font-lock-keywords259'(("^[ \t\n]*\\(<<\\)\\([^>]*\\)\\(>>=?\\)"260(1 font-lock-reference-face prepend )261(2 font-lock-keyword-face prepend)262(3 font-lock-reference-face prepend))263("^@[ \t\n]+"264(0 font-lock-reference-face prepend )))))265266267(let ((r (cons (marker-position (cdr (aref ess-noweb-chunk-vector268chunk-num)))269(marker-position (cdr (aref ess-noweb-chunk-vector270(1+ chunk-num))))))271(font-latex-extend-region-functions nil);; don't extend anything272(font-lock-extend-region-functions nil)) ;; this infloops :(273(save-restriction274(narrow-to-region (car r) (cdr r))275;; (sit-for 3)276(font-lock-fontify-region (car r) (cdr r)))277t)))278279(defadvice font-lock-after-change-function (around ess-remove-fl-multiline-extension activate)280"Remove `font-lock-extend-region-multiline' from `font-lock-extend-region-multiline'.281On commenting the whole chunk, causes infloop in282`after-change-functions' for no clear reason."283(let ((font-lock-extend-region-functions font-lock-extend-region-functions)284;; (font-latex-extend-region-functions nil)285)286(delq 'font-lock-extend-region-multiline font-lock-extend-region-functions)287ad-do-it))288289;; (ad-remove-advice 'font-lock-after-change-function 'around 'test-font)290;; (ad-activate 'font-lock-after-change-function)291292293(defun ess-noweb-font-lock-fontify-this-chunk ()294"Fontify this chunk according to its own major mode.295Since we are in the chunk, the major mode will already have been set296by ess-noweb-mode.el"297(interactive)298(ess-noweb-font-lock-fontify-chunk-by-number (ess-noweb-find-chunk-index-buffer)))299300(defun ess-noweb-font-lock-initial-fontify-buffer ()301"Applies syntax highlighting to some or all chunks in a noweb buffer.302The number of chunks is set by ess-noweb-font-lock-max-initial-chunks: if303this is nil, the entire buffer is fontified.304It is intended to be called when first entering ess-noweb-font-lock-mode.305For other purposes, use ess-noweb-font-lock-fontify-chunks."306(interactive)307;; This will be tricky. It will be very slow to go throught the chunks308;; in order, switching major modes all the time.309;; So, we will do the documentation in one pass, the code in a second310;; pass. This could still be a little slow if we have to swap between311;; different code modes regularly, but it should be bearable. It should312;; only happen when the file is first read in, anyway313(save-excursion314(let (start-chunk end-chunk this-chunk chunk-counter)315(setq this-chunk (ess-noweb-find-chunk-index-buffer))316(if ess-noweb-font-lock-max-initial-chunks317(progn318(setq start-chunk319(max 0320(- this-chunk321(/ ess-noweb-font-lock-max-initial-chunks 2))))322;; Don't you just love hairy lisp syntax ? The above means set the323;; starting chunk to the current chunk minus half of324;; ess-noweb-font-lock-max-initial-chunks, unless that is negative in325;; which case set it to 0326(setq end-chunk (+ start-chunk ess-noweb-font-lock-max-initial-chunks))327(if (> end-chunk (- (length ess-noweb-chunk-vector) 2))328(setq end-chunk (- (length ess-noweb-chunk-vector) 2))))329;; If ess-noweb-font-lock-max-initial-chunks is nil, do the whole buffer330(progn331(setq start-chunk 0)332(setq end-chunk (- (length ess-noweb-chunk-vector) 2))))333(ess-noweb-font-lock-fontify-chunks start-chunk end-chunk))))334335(defun ess-noweb-font-lock-fontify-buffer ()336"This function will fontify each chunk in the buffer appropriately."337(interactive)338(let ((start-chunk 0)339(end-chunk (- (length ess-noweb-chunk-vector) 2)))340(ess-noweb-font-lock-fontify-chunks start-chunk end-chunk)))341342(defun ess-noweb-font-lock-fontify-chunks (start-chunk end-chunk)343"Fontify a noweb file from start-chunk to end-chunk"344(interactive)345(let (chunk-counter)346(save-excursion347(message "Fontifying from %d to %d" start-chunk end-chunk)348;; Want to set DOC mode for the first Doc chunk, not for the others349(setq chunk-counter start-chunk)350(while (stringp (car (aref ess-noweb-chunk-vector chunk-counter)))351(setq chunk-counter (+ chunk-counter 1)))352(goto-char (cdr (aref ess-noweb-chunk-vector chunk-counter)))353(ess-noweb-select-mode)354;; Now go through the chunks, fontifying the documentation ones.355(while (<= chunk-counter end-chunk)356(if (not (stringp (car (aref ess-noweb-chunk-vector chunk-counter))))357(ess-noweb-font-lock-fontify-chunk-by-number chunk-counter))358(message "Fontifying documentation chunks: chunk %d" chunk-counter)359(setq chunk-counter (+ 1 chunk-counter)))360;; Go back to the start and go through the chunks, fontifying the code ones.361(setq chunk-counter start-chunk)362(message "About to do code chunks")363(while (<= chunk-counter end-chunk)364(when (stringp (car (aref ess-noweb-chunk-vector chunk-counter)))365;; It's a code chunk: goto it to set the correct code mode, then366;; fontify it.367(message "Fontifying code chunks: chunk %d" chunk-counter)368(goto-char (cdr (aref ess-noweb-chunk-vector chunk-counter)))369(ess-noweb-select-mode)370(ess-noweb-font-lock-fontify-this-chunk))371(setq chunk-counter (1+ chunk-counter))))372(ess-noweb-select-mode)))373374(defun ess-noweb-font-lock-mode-fn()375"Function that is intended to be attached to ess-noweb-font-lock-mode-hook."376(ess-noweb-font-lock-initial-fontify-buffer))377378;; This is a wee bit of a hack. If people attach `turn-on-font-lock'379;; to their major mode hook, it will play hell with380;; ess-noweb-font-lock-mode. I had hoped that providing a replacement381;; `nw-turn-on-font-lock' would solve the problem, but it didn't382;; (sometimes turn-on-font-lock appears in places other than383;; `.emacs', such as in ESS). So rather than have it fall over if384;; turn-on-lock was around, I redefined turn-on-font-lock to do the385;; right thing.386387(defvar ess-noweb-old-turn-on-font-lock nil)388389(defun nw-turn-on-font-lock ()390"Turn on font-lock mode, with due regard to whether we are in ess-noweb-mode"391(if (not ess-noweb-mode)392(ess-noweb-old-turn-on-font-lock)393(if (and (not ess-noweb-font-lock-mode) ess-noweb-use-font-lock-mode)394(ess-noweb-font-lock-mode ))))395396(unless (functionp 'ess-noweb-old-turn-on-font-lock)397(fset 'ess-noweb-old-turn-on-font-lock (symbol-function 'turn-on-font-lock))398(fset 'turn-on-font-lock (symbol-function 'nw-turn-on-font-lock)))399400(provide 'ess-noweb-font-lock-mode)401;; *****402;;403;; Adnan Yaqub ([email protected])404;; ORGA Kartensysteme GmbH // An der Kapelle 2 // D-33104 Paderborn // Germany405;; Tel. +49 5254 991-823 //Fax. +49 5254 991-749406407408409;; Local Variables:410;; mode:emacs-lisp411;; End:412413;;; ess-noweb-font-lock-mode.el ends here414415416