1 ;;; muse-colors.el --- Coloring and highlighting used by Muse
3 ;; Copyright (C) 2004, 2005 Free Software Foundation, Inc.
5 ;; Emacs Lisp Archive Entry
6 ;; Filename: muse-colors.el
8 ;; Date: Thu 11-Mar-2004
9 ;; Keywords: hypermedia
10 ;; Author: John Wiegley (johnw AT gnu DOT org)
11 ;; Maintainer: Michael Olson (mwolson AT gnu DOT org)
12 ;; URL: http://www.mwolson.org/projects/MuseMode.html
13 ;; Compatibility: Emacs21
15 ;; This file is not part of GNU Emacs.
17 ;; This is free software; you can redistribute it and/or modify it under
18 ;; the terms of the GNU General Public License as published by the Free
19 ;; Software Foundation; either version 2, or (at your option) any later
22 ;; This is distributed in the hope that it will be useful, but WITHOUT
23 ;; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
24 ;; FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
27 ;; You should have received a copy of the GNU General Public License
28 ;; along with GNU Emacs; see the file COPYING. If not, write to the
29 ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
30 ;; MA 02111-1307, USA.
34 ;; This file is the part of the Muse project that describes regexps
35 ;; that are used throughout the project.
45 ;; Lan Yufeng (nlany DOT web AT gmail DOT com) found an error where
46 ;; headings were being given the wrong face, contributing a patch to
51 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
53 ;; Emacs Muse Highlighting
55 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
58 (require 'muse-regexps
)
61 (defgroup muse-colors nil
62 "Options controlling the behaviour of Emacs Muse highlighting.
63 See `muse-colors-buffer' for more information."
66 (defun muse-make-faces ()
67 (mapc (lambda (newsym)
70 (setq newsym
(intern (concat "muse-header-"
71 (int-to-string num
))))
74 (eval `(defface ,newsym
76 ,(nth (1- num
) '("24pt" "18pt" "14pt" "12pt"))
79 :group
'muse-colors
)))
80 ((< emacs-major-version
21)
81 (copy-face 'default newsym
))
83 (eval `(defface ,newsym
84 '((t (:height
,(1+ (* 0.1 (- 5 num
)))
85 :inherit variable-pitch
88 :group
'muse-colors
))))))
92 (defface muse-link-face
93 '((((class color
) (background light
))
94 (:foreground
"green" :underline
"green" :bold t
))
95 (((class color
) (background dark
))
96 (:foreground
"cyan" :underline
"cyan" :bold t
))
98 "Face for Muse cross-references."
101 (defface muse-bad-link-face
102 '((((class color
) (background light
))
103 (:foreground
"red" :underline
"red" :bold t
))
104 (((class color
) (background dark
))
105 (:foreground
"coral" :underline
"coral" :bold t
))
107 "Face for bad Muse cross-references."
110 (defcustom muse-colors-buffer-hook nil
111 "A hook run after a region is highlighted.
112 Each function receives three arguments: BEG END VERBOSE.
113 BEG and END mark the range being highlighted, and VERBOSE specifies
114 whether progress messages should be displayed to the user."
118 (defvar muse-colors-regexp nil
)
119 (defvar muse-colors-vector nil
)
121 (defun muse-configure-highlighting (sym val
)
122 (setq muse-colors-regexp
123 (concat "\\(" (mapconcat (function
125 (if (symbolp (car rule
))
126 (symbol-value (car rule
))
127 (car rule
)))) val
"\\|") "\\)")
128 muse-colors-vector
(make-vector 128 nil
))
131 (if (eq (cadr (car rules
)) t
)
134 (unless (aref muse-colors-vector i
)
135 (aset muse-colors-vector i
(nth 2 (car rules
))))
137 (aset muse-colors-vector
(cadr (car rules
))
138 (nth 2 (car rules
))))
139 (setq rules
(cdr rules
))))
145 (defun muse-colors-emphasized ()
146 ;; here we need to check four different points - the start and end of the
147 ;; leading *s, and the start and end of the trailing *s. we allow the
148 ;; outsides to be surrounded by whitespace or punctuation, but no word
149 ;; characters, and the insides must not be surrounded by whitespace or
150 ;; punctuation. thus the following are valid:
153 ;; and the following is invalid:
155 (let* ((beg (match-beginning 0))
159 (unless (get-text-property beg
'invisible
)
160 ;; check if it's a header
161 (if (eq (char-after e1
) ?\
)
162 (if (or (= beg
(point-min))
163 (eq (char-before beg
) ?
\n))
165 (line-beginning-position) (line-end-position)
166 (list 'face
(intern (concat "muse-header-"
167 (int-to-string leader
))))))
169 (skip-chars-forward "^*\n" end
)
170 (when (eq (char-after) ?
\n)
172 (skip-chars-forward "^*" end
))
174 (skip-chars-forward "*" end
)
176 (add-text-properties beg e1
'(invisible t
))
178 e1 b2
(list 'face
(cond ((= leader
1) 'italic
)
180 ((= leader
3) 'bold-italic
))))
181 (add-text-properties b2 e2
'(invisible t
))
184 (add-text-properties beg e2
'(font-lock-multiline t
))))))))
186 (defun muse-colors-underlined ()
187 (let ((start (match-beginning 0)))
188 (when (search-forward "_" end t
)
189 (add-text-properties start
(+ start
1) '(invisible t
))
190 (add-text-properties (+ start
1) (match-beginning 0) '(face underline
))
191 (add-text-properties (match-beginning 0) (match-end 0) '(invisible t
)))))
193 (defun muse-colors-verbatim ()
194 (skip-chars-forward (concat "^" muse-regexp-space
"=>") end
))
196 (defcustom muse-colors-markup
197 `(;; render in teletype and suppress further parsing
201 ?
= muse-colors-verbatim
)
203 ;; make emphasized text appear emphasized
204 ("\\*\\{1,4\\}" ?
* muse-colors-emphasized
)
206 ;; make underlined text appear underlined
210 ?_ muse-colors-underlined
)
212 ("^#title" ?\
# muse-colors-title
)
214 (muse-link-regexp ?\
[ muse-colors-link
)
216 ;; highlight any markup tags encountered
217 (muse-tag-regexp ?\
< muse-colors-custom-tags
)
219 "Expressions to highlight an Emacs Muse buffer.
220 These are arranged in a rather special fashion, so as to be as quick as
223 Each element of the list is itself a list, of the form:
225 (LOCATE-REGEXP TEST-CHAR MATCH-FUNCTION)
227 LOCATE-REGEXP is a partial regexp, and should be the smallest possible
228 regexp to differentiate this rule from other rules. It may also be a
229 symbol containing such a regexp. The buffer region is scanned only
230 once, and LOCATE-REGEXP indicates where the scanner should stop to
231 look for highlighting possibilities.
233 TEST-CHAR is a char or t. The character should match the beginning
234 text matched by LOCATE-REGEXP. These chars are used to build a vector
235 for fast MATCH-FUNCTION calling.
237 MATCH-FUNCTION is the function called when a region has been
238 identified. It is responsible for adding the appropriate text
239 properties to change the appearance of the buffer.
241 This markup is used to modify the appearance of the original text to
242 make it look more like the published HTML would look (like making some
243 markup text invisible, inlining images, etc).
245 font-lock is used to apply the markup rules, so that they can happen
246 on a deferred basis. They are not always accurate, but you can use
247 \\[font-lock-fontifty-block] near the point of error to force
248 fontification in that area.
250 Lastly, none of the regexp should contain grouping elements that will
251 affect the match data results."
253 (list :tag
"Highlight rule"
254 (choice (regexp :tag
"Locate regexp")
255 (symbol :tag
"Regexp symbol"))
256 (choice (character :tag
"Confirm character")
257 (const :tag
"Default rule" t
))
259 :set
'muse-configure-highlighting
262 ;; XEmacs users don't have `font-lock-multiline'.
263 (unless (boundp 'font-lock-multiline
)
264 (defvar font-lock-multiline nil
))
266 (defun muse-use-font-lock ()
267 (set (make-local-variable 'font-lock-multiline
) 'undecided
)
268 (set (make-local-variable 'font-lock-defaults
)
269 `(nil t nil nil
'beginning-of-line
270 (font-lock-fontify-region-function . muse-colors-region
)
271 (font-lock-unfontify-region-function
272 . muse-unhighlight-region
)))
273 (set (make-local-variable 'font-lock-fontify-region-function
)
275 (set (make-local-variable 'font-lock-unfontify-region-function
)
276 'muse-unhighlight-region
)
279 (defun muse-colors-buffer ()
280 "Re-highlight the entire Muse buffer."
282 (muse-colors-region (point-min) (point-max) t
))
284 (defun muse-colors-region (beg end
&optional verbose
)
285 "Apply highlighting according to `muse-colors-markup'.
286 Note that this function should NOT change the buffer, nor should any
287 of the functions listed in `muse-colors-markup'."
288 (let ((buffer-undo-list t
)
289 (inhibit-read-only t
)
290 (inhibit-point-motion-hooks t
)
291 (inhibit-modification-hooks t
)
292 (modified-p (buffer-modified-p))
298 ;; check to see if we should expand the beg/end area for
299 ;; proper multiline matches
300 (when (and font-lock-multiline
302 (get-text-property (1- beg
) 'font-lock-multiline
))
303 ;; We are just after or in a multiline match.
304 (setq beg
(or (previous-single-property-change
305 beg
'font-lock-multiline
)
308 (setq beg
(line-beginning-position)))
309 (when font-lock-multiline
310 (setq end
(or (text-property-any end
(point-max)
311 'font-lock-multiline nil
)
314 (setq end
(line-beginning-position 2))
315 ;; Undo any fontification in the area.
316 (font-lock-unfontify-region beg end
)
317 ;; And apply fontification based on `muse-colors-markup'
318 (let ((len (float (- end beg
)))
319 (case-fold-search nil
)
322 (while (re-search-forward muse-colors-regexp end t
)
324 (message "Highlighting buffer...%d%%"
325 (* (/ (float (- (point) beg
)) len
) 100)))
327 (aref muse-colors-vector
328 (char-after (match-beginning 0))))
329 (when markup-func
(funcall markup-func
)))
330 (run-hook-with-args 'muse-colors-buffer-hook
332 (if verbose
(message "Highlighting buffer...done")))))
333 (set-buffer-modified-p modified-p
))))
335 (defcustom muse-colors-tags
336 '(("example" t nil muse-colors-example-tag
))
337 "A list of tag specifications for specially highlighting text.
338 XML-style tags are the best way to add custom highlighting to Muse.
339 This is easily accomplished by customizing this list of markup tags.
341 For each entry, the name of the tag is given, whether it expects
342 a closing tag and/or an optional set of attributes, and a
343 function that performs whatever action is desired within the
346 The function is called with three arguments, the beginning and
347 end of the region surrounded by the tags. If properties are
348 allowed, they are passed as a third argument in the form of an
349 alist. The `end' argument to the function is the last character
350 of the enclosed tag or region.
352 Functions should not modify the contents of the buffer.")
354 (defsubst muse-colors-tag-info
(tagname &rest args
)
355 (assoc tagname muse-colors-tags
))
357 (defun muse-colors-custom-tags ()
358 "Highlight `muse-colors-tags'."
359 (let ((tag-info (muse-colors-tag-info (match-string 4))))
361 (let ((closed-tag (match-string 3))
362 (start (match-beginning 0))
363 (beg (point)) end attrs
)
364 (when (nth 2 tag-info
)
365 (let ((attrstr (match-string 2)))
367 (string-match (concat "\\([^"
370 "\\([^\"]+\\)\"\\)?")
372 (let ((attr (cons (downcase
373 (match-string-no-properties 1 attrstr
))
374 (match-string-no-properties 3 attrstr
))))
375 (setq attrstr
(replace-match "" t t attrstr
))
377 (nconc attrs
(list attr
))
378 (setq attrs
(list attr
)))))))
379 (if (and (cadr tag-info
) (not closed-tag
))
380 (if (search-forward (concat "</" (car tag-info
) ">") nil t
)
381 (setq end
(match-end 0))
382 (setq tag-info nil
)))
384 (let ((args (list start end
)))
386 (nconc args
(list attrs
)))
387 (apply (nth 3 tag-info
) args
)))))))
389 (defun muse-colors-example-tag (beg end
)
390 "Strip properties from stuff in example."
391 (set-text-properties beg end nil
)
394 (defun muse-unhighlight-region (begin end
&optional verbose
)
395 "Remove all visual highlights in the buffer (except font-lock)."
396 (let ((buffer-undo-list t
)
397 (inhibit-read-only t
)
398 (inhibit-point-motion-hooks t
)
399 (inhibit-modification-hooks t
)
400 (modified-p (buffer-modified-p))
403 (remove-text-properties
404 begin end
'(face nil font-lock-multiline nil
405 invisible nil intangible nil display nil
406 mouse-face nil keymap nil help-echo nil
))
407 (set-buffer-modified-p modified-p
))))
409 (defvar muse-mode-local-map
410 (let ((map (make-sparse-keymap)))
411 (define-key map
[return] 'muse-follow-name-at-point)
412 (define-key map [(control ?m)] 'muse-follow-name-at-point)
413 (define-key map [(shift return)] 'muse-follow-name-at-point-other-window)
414 (if (featurep 'xemacs)
416 (define-key map [(button2)] 'muse-follow-name-at-mouse)
417 (define-key map [(shift button2)] 'muse-follow-name-at-mouse-other-window))
418 (define-key map [(shift control ?m)] 'muse-follow-name-at-point-other-window)
419 (define-key map [mouse-2] 'muse-follow-name-at-mouse)
420 (define-key map [(shift mouse-2)] 'muse-follow-name-at-mouse-other-window)
421 (unless (eq emacs-major-version 21)
422 (set-keymap-parent map muse-mode-map)))
424 "Local keymap used by Muse while on a link.")
426 (defvar muse-keymap-property
427 (if (or (featurep 'xemacs)
428 (>= emacs-major-version 21))
432 (defsubst muse-link-properties (help-str &optional face)
434 (list 'face face 'rear-nonsticky t
435 muse-keymap-property muse-mode-local-map)
436 (list 'invisible t 'intangible t 'rear-nonsticky t
437 muse-keymap-property muse-mode-local-map))
438 (list 'mouse-face 'highlight
440 muse-keymap-property muse-mode-local-map)))
442 (defun muse-link-face (link-name)
443 "Return the type of LINK-NAME as a face symbol - either a normal link, or a
446 (if (or (string-match muse-file-regexp link-name)
447 (string-match muse-url-regexp link-name))
449 (if (not (featurep 'muse-project))
451 (if (string-match "#" link-name)
452 (setq link-name (substring link-name 0 (match-beginning 0))))
453 (if (or (and (muse-project-of-file)
454 (muse-project-page-file link-name muse-current-project t))
455 (file-exists-p link-name))
457 'muse-bad-link-face)))))
459 (defun muse-colors-link ()
460 (when (eq ?\[ (char-after (match-beginning 0)))
461 ;; remove flyspell overlays
462 (when (fboundp 'flyspell-unhighlight-at)
463 (let ((cur (match-beginning 0)))
464 (while (> (match-end 0) cur)
465 (flyspell-unhighlight-at cur)
466 (setq cur (1+ cur)))))
467 (let* ((link (match-string-no-properties 2))
468 (desc (match-string-no-properties 3))
469 (props (muse-link-properties
470 desc (muse-link-face (match-string 2))))
471 (invis-props (append props (muse-link-properties desc))))
474 ;; we put the normal face properties on the invisible
475 ;; portion too, since emacs sometimes will position
476 ;; the cursor on an intangible character
477 (add-text-properties (match-beginning 0)
478 (match-beginning 3) invis-props)
479 (add-text-properties (match-beginning 3) (match-end 3) props)
480 (add-text-properties (match-end 3) (match-end 0) invis-props))
481 (add-text-properties (match-beginning 0)
482 (match-beginning 2) invis-props)
483 (add-text-properties (match-beginning 2) (match-end 0) props)
484 (add-text-properties (match-end 2) (match-end 0) invis-props)))
485 (goto-char (match-end 0))
487 (match-beginning 0) (match-end 0)
488 (muse-link-properties (match-string-no-properties 0)
489 (muse-link-face (match-string 2))))
490 (goto-char (match-end 0))))
492 (defun muse-colors-title ()
493 (add-text-properties (+ 7 (match-beginning 0))
495 '(face muse-header-1)))
497 (provide 'muse-colors)
499 ;;; muse-colors.el ends here