summaryrefslogtreecommitdiffstats
path: root/emacs.d/elisp/muse/muse-colors.el
diff options
context:
space:
mode:
Diffstat (limited to 'emacs.d/elisp/muse/muse-colors.el')
-rw-r--r--emacs.d/elisp/muse/muse-colors.el1022
1 files changed, 1022 insertions, 0 deletions
diff --git a/emacs.d/elisp/muse/muse-colors.el b/emacs.d/elisp/muse/muse-colors.el
new file mode 100644
index 0000000..fb76ac5
--- /dev/null
+++ b/emacs.d/elisp/muse/muse-colors.el
@@ -0,0 +1,1022 @@
+;;; muse-colors.el --- coloring and highlighting used by Muse
+
+;; Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010
+;; Free Software Foundation, Inc.
+
+;; Author: John Wiegley (johnw AT gnu DOT org)
+;; Keywords: hypermedia
+;; Date: Thu 11-Mar-2004
+
+;; This file is part of Emacs Muse. It is not part of GNU Emacs.
+
+;; Emacs Muse is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published
+;; by the Free Software Foundation; either version 3, or (at your
+;; option) any later version.
+
+;; Emacs Muse is distributed in the hope that it will be useful, but
+;; WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+;; General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with Emacs Muse; see the file COPYING. If not, write to the
+;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+;; Boston, MA 02110-1301, USA.
+
+;;; Commentary:
+
+;;; Contributors:
+
+;; Lan Yufeng (nlany DOT web AT gmail DOT com) found an error where
+;; headings were being given the wrong face, contributing a patch to
+;; fix this.
+
+;; Sergey Vlasov (vsu AT altlinux DOT ru) fixed an issue with coloring
+;; links that are in consecutive lines.
+
+;; Jim Ottaway ported the <lisp> tag from emacs-wiki.
+
+;; Per B. Sederberg (per AT med DOT upenn DOT edu) contributed the
+;; viewing of inline images.
+
+;;; Code:
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;
+;; Emacs Muse Highlighting
+;;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+(require 'muse-mode)
+(require 'muse-regexps)
+(require 'font-lock)
+
+(defgroup muse-colors nil
+ "Options controlling the behavior of Emacs Muse highlighting.
+See `muse-colors-buffer' for more information."
+ :group 'muse-mode)
+
+(defcustom muse-colors-autogen-headings t
+ "Specify whether the heading faces should be auto-generated.
+The default is to scale them.
+
+Choosing 'outline will copy the colors from the outline-mode
+headings.
+
+If you want to customize each of the headings individually, set
+this to nil."
+ :type '(choice (const :tag "Default (scaled) headings" t)
+ (const :tag "Use outline-mode headings" outline)
+ (const :tag "Don't touch the headings" nil))
+ :group 'muse-colors)
+
+(defcustom muse-colors-evaluate-lisp-tags t
+ "Specify whether to evaluate the contents of <lisp> tags at
+display time. If nil, don't evaluate them. If non-nil, evaluate
+them.
+
+The actual contents of the buffer are not changed, only the
+displayed text."
+ :type 'boolean
+ :group 'muse-colors)
+
+(defcustom muse-colors-inline-images t
+ "Specify whether to inline images inside the Emacs buffer. If
+nil, don't inline them. If non-nil, an image link will be
+replaced by the image.
+
+The actual contents of the buffer are not changed, only whether
+an image is displayed."
+ :type 'boolean
+ :group 'muse-colors)
+
+(defcustom muse-colors-inline-image-method 'default-directory
+ "Determine how to locate inline images.
+Setting this to 'default-directory uses the current directory of
+the current Muse buffer.
+
+Setting this to a function calls that function with the filename
+of the image to be inlined. The value that is returned will be
+used as the filename of the image."
+ :type '(choice (const :tag "Current directory" default-directory)
+ (const :tag "Publishing directory"
+ muse-colors-use-publishing-directory)
+ (function :tag "Custom function"))
+ :group 'muse-colors)
+
+(defvar muse-colors-region-end nil
+ "Indicate the end of the region that is currently being font-locked.")
+(make-variable-buffer-local 'muse-colors-region-end)
+
+;;;###autoload
+(defun muse-colors-toggle-inline-images ()
+ "Toggle display of inlined images on/off."
+ (interactive)
+ ;; toggle the custom setting
+ (if (not muse-colors-inline-images)
+ (setq muse-colors-inline-images t)
+ (setq muse-colors-inline-images nil))
+ ;; reprocess the buffer
+ (muse-colors-buffer)
+ ;; display informative message
+ (if muse-colors-inline-images
+ (message "Images are now displayed inline")
+ (message "Images are now displayed as links")))
+
+(defvar muse-colors-outline-faces-list
+ (if (facep 'outline-1)
+ '(outline-1 outline-2 outline-3 outline-4 outline-5)
+ ;; these are equivalent in coloring to the outline faces
+ '(font-lock-function-name-face
+ font-lock-variable-name-face
+ font-lock-keyword-face
+ font-lock-builtin-face
+ font-lock-comment-face))
+ "Outline faces to use when assigning Muse header faces.")
+
+(defun muse-make-faces-default (&optional later)
+ "Generate the default face definitions for headers."
+ (dolist (num '(1 2 3 4 5))
+ (let ((newsym (intern (concat "muse-header-" (int-to-string num))))
+ (docstring (concat
+ "Muse header face. See "
+ "`muse-colors-autogen-headings' before changing it.")))
+ ;; put in the proper group and give documentation
+ (if later
+ (unless (featurep 'xemacs)
+ (muse-copy-face 'variable-pitch newsym)
+ (set-face-attribute newsym nil :height (1+ (* 0.1 (- 5 num)))
+ :weight 'bold))
+ (if (featurep 'xemacs)
+ (eval `(defface ,newsym
+ '((t (:size
+ ,(nth (1- num)
+ '("24pt" "18pt" "14pt" "12pt" "11pt"))
+ :bold t)))
+ ,docstring
+ :group 'muse-colors))
+ (eval `(defface ,newsym
+ '((t (:height ,(1+ (* 0.1 (- 5 num)))
+ :inherit variable-pitch
+ :weight bold)))
+ ,docstring
+ :group 'muse-colors)))))))
+
+(progn (muse-make-faces-default))
+
+(defun muse-make-faces (&optional frame)
+ "Generate face definitions for headers based the user's preferences."
+ (cond
+ ((not muse-colors-autogen-headings)
+ nil)
+ ((eq muse-colors-autogen-headings t)
+ (muse-make-faces-default t))
+ (t
+ (dolist (num '(1 2 3 4 5))
+ (let ((newsym (intern (concat "muse-header-" (int-to-string num)))))
+ ;; copy the desired face definition
+ (muse-copy-face (nth (1- num) muse-colors-outline-faces-list)
+ newsym))))))
+
+;; after displaying the Emacs splash screen, the faces are wiped out,
+;; so recover from that
+(add-hook 'window-setup-hook #'muse-make-faces)
+;; ditto for when a new frame is created
+(when (boundp 'after-make-frame-functions)
+ (add-hook 'after-make-frame-functions #'muse-make-faces))
+
+(defface muse-link
+ '((((class color) (background light))
+ (:foreground "blue" :underline "blue" :bold t))
+ (((class color) (background dark))
+ (:foreground "cyan" :underline "cyan" :bold t))
+ (t (:bold t)))
+ "Face for Muse cross-references."
+ :group 'muse-colors)
+
+(defface muse-bad-link
+ '((((class color) (background light))
+ (:foreground "red" :underline "red" :bold t))
+ (((class color) (background dark))
+ (:foreground "coral" :underline "coral" :bold t))
+ (t (:bold t)))
+ "Face for bad Muse cross-references."
+ :group 'muse-colors)
+
+(defface muse-verbatim
+ '((((class color) (background light))
+ (:foreground "slate gray"))
+ (((class color) (background dark))
+ (:foreground "gray")))
+ "Face for verbatim text."
+ :group 'muse-colors)
+
+(defface muse-emphasis-1
+ '((t (:italic t)))
+ "Face for italic emphasized text."
+ :group 'muse-colors)
+
+(defface muse-emphasis-2
+ '((t (:bold t)))
+ "Face for bold emphasized text."
+ :group 'muse-colors)
+
+(defface muse-emphasis-3
+ '((t (:bold t :italic t)))
+ "Face for bold italic emphasized text."
+ :group 'muse-colors)
+
+(muse-copy-face 'italic 'muse-emphasis-1)
+(muse-copy-face 'bold 'muse-emphasis-2)
+(muse-copy-face 'bold-italic 'muse-emphasis-3)
+
+(defcustom muse-colors-buffer-hook nil
+ "A hook run after a region is highlighted.
+Each function receives three arguments: BEG END VERBOSE.
+BEG and END mark the range being highlighted, and VERBOSE specifies
+whether progress messages should be displayed to the user."
+ :type 'hook
+ :group 'muse-colors)
+
+(defvar muse-colors-highlighting-registry nil
+ "The rules for highlighting Muse and Muse-derived buffers.
+This is automatically generated when using font-lock in Muse buffers.
+
+This an alist of major-mode symbols to `muse-colors-rule' objects.")
+
+(defun muse-colors-make-highlighting-struct ()
+ (list nil nil nil))
+(defconst muse-colors-highlighting.regexp 0
+ "Regexp matching each car of the markup of the current rule.")
+(defconst muse-colors-highlighting.vector 1
+ "Vector of all characters that are part of the markup of the current rule.
+This is composed of the 2nd element of each markup entry.")
+(defconst muse-colors-highlighting.remaining 2
+ "Expressions for highlighting a buffer which have no corresponding
+entry in the vector.")
+
+(defsubst muse-colors-highlighting-entry (mode)
+ "Return the highlighting rules for MODE."
+ (assq mode muse-colors-highlighting-registry))
+
+(defun muse-colors-find-highlighting (mode)
+ "Return the highlighting rules to be used for MODE.
+If MODE does not have highlighting rules, check its parent modes."
+ (let ((seen nil))
+ (catch 'rules
+ (while (and mode (not (memq mode seen)))
+ (let ((entry (muse-colors-highlighting-entry mode)))
+ (when entry (throw 'rules (cdr entry))))
+ (setq seen (cons mode seen))
+ (setq mode (get mode 'derived-mode-parent)))
+ nil)))
+
+(defun muse-colors-define-highlighting (mode markup)
+ "Create or update the markup rules for MODE, using MARKUP.
+
+See `muse-colors-markup' for an explanation of the format that MARKUP
+should take."
+ (unless (and (symbolp mode) mode (consp markup))
+ (error "Invalid arguments"))
+ (let* ((highlighting-entry (muse-colors-highlighting-entry mode))
+ (struct (cdr highlighting-entry))
+ (regexp nil)
+ (vector nil)
+ (remaining nil))
+ ;; Initialize struct
+ (if struct
+ (setq vector (nth muse-colors-highlighting.vector struct))
+ (setq struct (muse-colors-make-highlighting-struct)))
+ ;; Initialize vector
+ (if vector
+ (let ((i 0))
+ (while (< i 128)
+ (aset vector i nil)
+ (setq i (1+ i))))
+ (setq vector (make-vector 128 nil)))
+ ;; Determine vector, regexp, remaining
+ (let ((regexps nil)
+ (rules nil))
+ (dolist (rule markup)
+ (let ((value (cond ((symbolp (car rule))
+ (symbol-value (car rule)))
+ ((stringp (car rule))
+ (car rule))
+ (t nil))))
+ (when value
+ (setq rules (cons rule rules))
+ (setq regexps (cons value regexps)))))
+ (setq regexps (nreverse regexps))
+ (setq regexp (concat "\\(" (mapconcat #'identity regexps "\\|") "\\)"))
+ (dolist (rule rules)
+ (if (eq (nth 1 rule) t)
+ (setq remaining (cons (cons (nth 0 rule) (nth 2 rule))
+ remaining))
+ (aset vector (nth 1 rule)
+ (cons (cons (nth 0 rule) (nth 2 rule))
+ (aref vector (nth 1 rule)))))))
+ ;; Update the struct
+ (setcar (nthcdr muse-colors-highlighting.regexp struct) regexp)
+ (setcar (nthcdr muse-colors-highlighting.vector struct) vector)
+ (setcar (nthcdr muse-colors-highlighting.remaining struct) remaining)
+ ;; Update entry for mode in muse-colors-highlighting-registry
+ (if highlighting-entry
+ (setcdr highlighting-entry struct)
+ (setq muse-colors-highlighting-registry
+ (cons (cons mode struct)
+ muse-colors-highlighting-registry)))))
+
+(defun muse-configure-highlighting (sym val)
+ "Extract color markup information from VAL and set to SYM.
+This is usually called with `muse-colors-markup' as both arguments."
+ (muse-colors-define-highlighting 'muse-mode val)
+ (set sym val))
+
+(defun muse-colors-emphasized ()
+ "Color emphasized text and headings."
+ ;; Here we need to check four different points - the start and end
+ ;; of the leading *s, and the start and end of the trailing *s. We
+ ;; allow the outsides to be surrounded by whitespace or punctuation,
+ ;; but no word characters, and the insides must not be surrounded by
+ ;; whitespace or punctuation. Thus the following are valid:
+ ;;
+ ;; " *foo bar* "
+ ;; "**foo**,"
+ ;; and the following is invalid:
+ ;; "** testing **"
+ (let* ((beg (match-beginning 0))
+ (e1 (match-end 0))
+ (leader (- e1 beg))
+ b2 e2 multiline)
+ (unless (or (eq (get-text-property beg 'invisible) 'muse)
+ (get-text-property beg 'muse-comment)
+ (get-text-property beg 'muse-directive))
+ ;; check if it's a header
+ (if (eq (char-after e1) ?\ )
+ (when (or (= beg (point-min))
+ (eq (char-before beg) ?\n))
+ (add-text-properties
+ (muse-line-beginning-position) (muse-line-end-position)
+ (list 'face (intern (concat "muse-header-"
+ (int-to-string leader))))))
+ ;; beginning of line or space or symbol
+ (when (or (= beg (point-min))
+ (eq (char-syntax (char-before beg)) ?\ )
+ (memq (char-before beg)
+ '(?\- ?\[ ?\< ?\( ?\' ?\` ?\" ?\n)))
+ (save-excursion
+ (skip-chars-forward "^*<>\n" muse-colors-region-end)
+ (when (eq (char-after) ?\n)
+ (setq multiline t)
+ (skip-chars-forward "^*<>" muse-colors-region-end))
+ (setq b2 (point))
+ (skip-chars-forward "*" muse-colors-region-end)
+ (setq e2 (point))
+ ;; Abort if space exists just before end
+ ;; or bad leader
+ ;; or no '*' at end
+ ;; or word constituent follows
+ (unless (or (> leader 5)
+ (not (eq leader (- e2 b2)))
+ (eq (char-syntax (char-before b2)) ?\ )
+ (not (eq (char-after b2) ?*))
+ (and (not (eobp))
+ (eq (char-syntax (char-after (1+ b2))) ?w)))
+ (add-text-properties beg e1 '(invisible muse))
+ (add-text-properties
+ e1 b2 (list 'face (cond ((= leader 1) 'muse-emphasis-1)
+ ((= leader 2) 'muse-emphasis-2)
+ ((= leader 3) 'muse-emphasis-3))))
+ (add-text-properties b2 e2 '(invisible muse))
+ (when multiline
+ (add-text-properties
+ beg e2 '(font-lock-multiline t))))))))))
+
+(defun muse-colors-underlined ()
+ "Color underlined text."
+ (let ((start (match-beginning 0))
+ multiline)
+ (unless (or (eq (get-text-property start 'invisible) 'muse)
+ (get-text-property start 'muse-comment)
+ (get-text-property start 'muse-directive))
+ ;; beginning of line or space or symbol
+ (when (or (= start (point-min))
+ (eq (char-syntax (char-before start)) ?\ )
+ (memq (char-before start)
+ '(?\- ?\[ ?\< ?\( ?\' ?\` ?\" ?\n)))
+ (save-excursion
+ (skip-chars-forward "^_<>\n" muse-colors-region-end)
+ (when (eq (char-after) ?\n)
+ (setq multiline t)
+ (skip-chars-forward "^_<>" muse-colors-region-end))
+ ;; Abort if space exists just before end
+ ;; or no '_' at end
+ ;; or word constituent follows
+ (unless (or (eq (char-syntax (char-before (point))) ?\ )
+ (not (eq (char-after (point)) ?_))
+ (and (not (eobp))
+ (eq (char-syntax (char-after (1+ (point)))) ?w)))
+ (add-text-properties start (1+ start) '(invisible muse))
+ (add-text-properties (1+ start) (point) '(face underline))
+ (add-text-properties (point)
+ (min (1+ (point)) (point-max))
+ '(invisible muse))
+ (when multiline
+ (add-text-properties
+ start (min (1+ (point)) (point-max))
+ '(font-lock-multiline t)))))))))
+
+(defun muse-colors-verbatim ()
+ "Render in teletype and suppress further parsing."
+ (let ((start (match-beginning 0))
+ multiline)
+ (unless (or (eq (get-text-property start 'invisible) 'muse)
+ (get-text-property start 'muse-comment)
+ (get-text-property start 'muse-directive))
+ ;; beginning of line or space or symbol
+ (when (or (= start (point-min))
+ (eq (char-syntax (char-before start)) ?\ )
+ (memq (char-before start)
+ '(?\- ?\[ ?\< ?\( ?\' ?\` ?\" ?\n)))
+ (let ((pos (point)))
+ (skip-chars-forward "^=\n" muse-colors-region-end)
+ (when (eq (char-after) ?\n)
+ (setq multiline t)
+ (skip-chars-forward "^=" muse-colors-region-end))
+ ;; Abort if space exists just before end
+ ;; or no '=' at end
+ ;; or word constituent follows
+ (unless (or (eq (char-syntax (char-before (point))) ?\ )
+ (not (eq (char-after (point)) ?=))
+ (and (not (eobp))
+ (eq (char-syntax (char-after (1+ (point)))) ?w)))
+ (setq pos (min (1+ (point)) (point-max)))
+ (add-text-properties start (1+ start) '(invisible muse))
+ (add-text-properties (1+ start) (point) '(face muse-verbatim))
+ (add-text-properties (point)
+ (min (1+ (point)) (point-max))
+ '(invisible muse))
+ (when multiline
+ (add-text-properties
+ start (min (1+ (point)) (point-max))
+ '(font-lock-multiline t))))
+ (goto-char pos))))))
+
+(defcustom muse-colors-markup
+ `(;; make emphasized text appear emphasized
+ ("\\*\\{1,5\\}" ?* muse-colors-emphasized)
+
+ ;; make underlined text appear underlined
+ (,(concat "_[^" muse-regexp-blank "_\n]")
+ ?_ muse-colors-underlined)
+
+ ("^#title " ?\# muse-colors-title)
+
+ (muse-explicit-link-regexp ?\[ muse-colors-explicit-link)
+
+ ;; render in teletype and suppress further parsing
+ (,(concat "=[^" muse-regexp-blank "=\n]") ?= muse-colors-verbatim)
+
+ ;; highlight any markup tags encountered
+ (muse-tag-regexp ?\< muse-colors-custom-tags)
+
+ ;; display comments
+ (,(concat "^;[" muse-regexp-blank "]") ?\; muse-colors-comment)
+
+ ;; this has to come later since it doesn't have a special
+ ;; character in the second cell
+ (muse-url-regexp t muse-colors-implicit-link)
+ )
+ "Expressions to highlight an Emacs Muse buffer.
+These are arranged in a rather special fashion, so as to be as quick as
+possible.
+
+Each element of the list is itself a list, of the form:
+
+ (LOCATE-REGEXP TEST-CHAR MATCH-FUNCTION)
+
+LOCATE-REGEXP is a partial regexp, and should be the smallest possible
+regexp to differentiate this rule from other rules. It may also be a
+symbol containing such a regexp. The buffer region is scanned only
+once, and LOCATE-REGEXP indicates where the scanner should stop to
+look for highlighting possibilities.
+
+TEST-CHAR is a char or t. The character should match the beginning
+text matched by LOCATE-REGEXP. These chars are used to build a vector
+for fast MATCH-FUNCTION calling.
+
+MATCH-FUNCTION is the function called when a region has been
+identified. It is responsible for adding the appropriate text
+properties to change the appearance of the buffer.
+
+This markup is used to modify the appearance of the original text to
+make it look more like the published HTML would look (like making some
+markup text invisible, inlining images, etc).
+
+font-lock is used to apply the markup rules, so that they can happen
+on a deferred basis. They are not always accurate, but you can use
+\\[font-lock-fontifty-block] near the point of error to force
+fontification in that area."
+ :type '(repeat
+ (list :tag "Highlight rule"
+ (choice (regexp :tag "Locate regexp")
+ (symbol :tag "Regexp symbol"))
+ (choice (character :tag "Confirm character")
+ (const :tag "Default rule" t))
+ function))
+ :set 'muse-configure-highlighting
+ :group 'muse-colors)
+
+;; XEmacs users don't have `font-lock-multiline'.
+(unless (boundp 'font-lock-multiline)
+ (defvar font-lock-multiline nil))
+
+(defun muse-use-font-lock ()
+ "Set up font-locking for Muse."
+ (muse-add-to-invisibility-spec 'muse)
+ (set (make-local-variable 'font-lock-multiline) 'undecided)
+ (set (make-local-variable 'font-lock-defaults)
+ `(nil t nil nil beginning-of-line
+ (font-lock-fontify-region-function . muse-colors-region)
+ (font-lock-unfontify-region-function
+ . muse-unhighlight-region)))
+ (set (make-local-variable 'font-lock-fontify-region-function)
+ 'muse-colors-region)
+ (set (make-local-variable 'font-lock-unfontify-region-function)
+ 'muse-unhighlight-region)
+ (muse-make-faces)
+ (muse-colors-define-highlighting 'muse-mode muse-colors-markup)
+ (font-lock-mode t))
+
+(defun muse-colors-buffer ()
+ "Re-highlight the entire Muse buffer."
+ (interactive)
+ (muse-colors-region (point-min) (point-max) t))
+
+(defvar muse-colors-fontifying-p nil
+ "Indicate whether Muse is fontifying the current buffer.")
+(make-variable-buffer-local 'muse-colors-fontifying-p)
+
+(defvar muse-colors-delayed-commands nil
+ "Commands to be run immediately after highlighting a region.
+
+This is meant to accommodate highlighting <lisp> in #title
+directives after everything else.
+
+It may be modified by Muse functions during highlighting, but not
+the user.")
+(make-variable-buffer-local 'muse-colors-delayed-commands)
+
+(defun muse-colors-region (beg end &optional verbose)
+ "Apply highlighting according to `muse-colors-markup'.
+Note that this function should NOT change the buffer, nor should any
+of the functions listed in `muse-colors-markup'."
+ (let ((buffer-undo-list t)
+ (inhibit-read-only t)
+ (inhibit-point-motion-hooks t)
+ (inhibit-modification-hooks t)
+ (modified-p (buffer-modified-p))
+ (muse-colors-fontifying-p t)
+ (muse-colors-region-end (muse-line-end-position end))
+ (muse-colors-delayed-commands nil)
+ (highlighting (muse-colors-find-highlighting major-mode))
+ regexp vector remaining
+ deactivate-mark)
+ (unless highlighting
+ (error "No highlighting found for this mode"))
+ (setq regexp (nth muse-colors-highlighting.regexp highlighting)
+ vector (nth muse-colors-highlighting.vector highlighting)
+ remaining (nth muse-colors-highlighting.remaining highlighting))
+ (unwind-protect
+ (save-excursion
+ (save-restriction
+ (widen)
+ ;; check to see if we should expand the beg/end area for
+ ;; proper multiline matches
+ (when (and font-lock-multiline
+ (> beg (point-min))
+ (get-text-property (1- beg) 'font-lock-multiline))
+ ;; We are just after or in a multiline match.
+ (setq beg (or (previous-single-property-change
+ beg 'font-lock-multiline)
+ (point-min)))
+ (goto-char beg)
+ (setq beg (muse-line-beginning-position)))
+ (when font-lock-multiline
+ (setq end (or (text-property-any end (point-max)
+ 'font-lock-multiline nil)
+ (point-max))))
+ (goto-char end)
+ (setq end (muse-line-beginning-position 2))
+ ;; Undo any fontification in the area.
+ (font-lock-unfontify-region beg end)
+ ;; And apply fontification based on `muse-colors-markup'
+ (let ((len (float (- end beg)))
+ (case-fold-search nil)
+ markup-list)
+ (goto-char beg)
+ (while (and (< (point) end)
+ (re-search-forward regexp end t))
+ (if verbose
+ (message "Highlighting buffer...%d%%"
+ (* (/ (float (- (point) beg)) len) 100)))
+ (let ((ch (char-after (match-beginning 0))))
+ (when (< ch 128)
+ (setq markup-list (aref vector ch))))
+ (unless markup-list
+ (setq markup-list remaining))
+ (let ((prev (point)))
+ ;; backtrack and figure out which rule matched
+ (goto-char (match-beginning 0))
+ (catch 'done
+ (dolist (entry markup-list)
+ (let ((value (cond ((symbolp (car entry))
+ (symbol-value (car entry)))
+ ((stringp (car entry))
+ (car entry))
+ (t nil))))
+ (when (and (stringp value) (looking-at value))
+ (goto-char (match-end 0))
+ (when (cdr entry)
+ (funcall (cdr entry)))
+ (throw 'done t))))
+ ;; if no rule matched, which should never happen,
+ ;; return to previous position so that forward
+ ;; progress is ensured
+ (goto-char prev))))
+ (dolist (command muse-colors-delayed-commands)
+ (apply (car command) (cdr command)))
+ (run-hook-with-args 'muse-colors-buffer-hook
+ beg end verbose)
+ (if verbose (message "Highlighting buffer...done")))))
+ (set-buffer-modified-p modified-p))))
+
+(defcustom muse-colors-tags
+ '(("example" t nil nil muse-colors-example-tag)
+ ("code" t nil nil muse-colors-example-tag)
+ ("verbatim" t nil nil muse-colors-literal-tag)
+ ("lisp" t t nil muse-colors-lisp-tag)
+ ("literal" t nil nil muse-colors-literal-tag))
+ "A list of tag specifications for specially highlighting text.
+XML-style tags are the best way to add custom highlighting to Muse.
+This is easily accomplished by customizing this list of markup tags.
+
+For each entry, the name of the tag is given, whether it expects
+a closing tag and/or an optional set of attributes, whether it is
+nestable, and a function that performs whatever action is desired
+within the delimited region.
+
+The function is called with three arguments, the beginning and
+end of the region surrounded by the tags. If properties are
+allowed, they are passed as a third argument in the form of an
+alist. The `end' argument to the function is the last character
+of the enclosed tag or region.
+
+Functions should not modify the contents of the buffer."
+ :type '(repeat (list (string :tag "Markup tag")
+ (boolean :tag "Expect closing tag" :value t)
+ (boolean :tag "Parse attributes" :value nil)
+ (boolean :tag "Nestable" :value nil)
+ function))
+ :group 'muse-colors)
+
+(defvar muse-colors-inhibit-tags-in-directives t
+ "If non-nil, don't allow tags to be interpreted in directives.
+This is used to delay highlighting of <lisp> tags in #title until later.")
+(make-variable-buffer-local 'muse-colors-inhibit-tags-in-directives)
+
+(defsubst muse-colors-tag-info (tagname &rest args)
+ "Get tag info associated with TAGNAME, ignoring ARGS."
+ (assoc tagname muse-colors-tags))
+
+(defun muse-colors-custom-tags ()
+ "Highlight `muse-colors-tags'."
+ (let ((tag-info (muse-colors-tag-info (match-string 1))))
+ (unless (or (not tag-info)
+ (get-text-property (match-beginning 0) 'muse-comment)
+ (and muse-colors-inhibit-tags-in-directives
+ (get-text-property (match-beginning 0) 'muse-directive)))
+ (let ((closed-tag (match-string 3))
+ (start (match-beginning 0))
+ end attrs)
+ (when (nth 2 tag-info)
+ (let ((attrstr (match-string 2)))
+ (while (and attrstr
+ (string-match (concat "\\([^"
+ muse-regexp-blank
+ "=\n]+\\)\\(=\""
+ "\\([^\"]+\\)\"\\)?")
+ attrstr))
+ (let ((attr (cons (downcase
+ (muse-match-string-no-properties 1 attrstr))
+ (muse-match-string-no-properties 3 attrstr))))
+ (setq attrstr (replace-match "" t t attrstr))
+ (if attrs
+ (nconc attrs (list attr))
+ (setq attrs (list attr)))))))
+ (if (and (cadr tag-info) (not closed-tag))
+ (if (muse-goto-tag-end (car tag-info) (nth 3 tag-info))
+ (setq end (match-end 0))
+ (setq tag-info nil)))
+ (when tag-info
+ (let ((args (list start end)))
+ (if (nth 2 tag-info)
+ (nconc args (list attrs)))
+ (apply (nth 4 tag-info) args)))))))
+
+(defun muse-unhighlight-region (begin end &optional verbose)
+ "Remove all visual highlights in the buffer (except font-lock)."
+ (let ((buffer-undo-list t)
+ (inhibit-read-only t)
+ (inhibit-point-motion-hooks t)
+ (inhibit-modification-hooks t)
+ (modified-p (buffer-modified-p))
+ deactivate-mark)
+ (unwind-protect
+ (remove-text-properties
+ begin end '(face nil font-lock-multiline nil end-glyph nil
+ invisible nil intangible nil display nil
+ mouse-face nil keymap nil help-echo nil
+ muse-link nil muse-directive nil muse-comment nil
+ muse-no-implicit-link nil muse-no-flyspell nil))
+ (set-buffer-modified-p modified-p))))
+
+(defun muse-colors-example-tag (beg end)
+ "Strip properties and colorize with `muse-verbatim'."
+ (muse-unhighlight-region beg end)
+ (let ((multi (save-excursion
+ (goto-char beg)
+ (forward-line 1)
+ (> end (point)))))
+ (add-text-properties beg end `(face muse-verbatim
+ font-lock-multiline ,multi))))
+
+(defun muse-colors-literal-tag (beg end)
+ "Strip properties and mark as literal."
+ (muse-unhighlight-region beg end)
+ (let ((multi (save-excursion
+ (goto-char beg)
+ (forward-line 1)
+ (> end (point)))))
+ (add-text-properties beg end `(font-lock-multiline ,multi))))
+
+(defun muse-colors-lisp-tag (beg end attrs)
+ "Color the region enclosed by a <lisp> tag."
+ (if (not muse-colors-evaluate-lisp-tags)
+ (muse-colors-literal-tag beg end)
+ (muse-unhighlight-region beg end)
+ (let (beg-lisp end-lisp)
+ (save-match-data
+ (goto-char beg)
+ (setq beg-lisp (and (looking-at "<[^>]+>")
+ (match-end 0)))
+ (goto-char end)
+ (setq end-lisp (and (muse-looking-back "</[^>]+>")
+ (match-beginning 0))))
+ (add-text-properties
+ beg end
+ (list 'font-lock-multiline t
+ 'display (muse-eval-lisp
+ (concat
+ "(progn "
+ (buffer-substring-no-properties beg-lisp end-lisp)
+ ")"))
+ 'intangible t)))))
+
+(defvar muse-mode-local-map
+ (let ((map (make-sparse-keymap)))
+ (define-key map [return] 'muse-follow-name-at-point)
+ (define-key map [(control ?m)] 'muse-follow-name-at-point)
+ (define-key map [(shift return)] 'muse-follow-name-at-point-other-window)
+ (if (featurep 'xemacs)
+ (progn
+ (define-key map [(button2)] 'muse-follow-name-at-mouse)
+ (define-key map [(shift button2)]
+ 'muse-follow-name-at-mouse-other-window))
+ (define-key map [(shift control ?m)]
+ 'muse-follow-name-at-point-other-window)
+ (define-key map [mouse-2] 'muse-follow-name-at-mouse)
+ (define-key map [(shift mouse-2)]
+ 'muse-follow-name-at-mouse-other-window)
+ (unless (eq emacs-major-version 21)
+ (set-keymap-parent map muse-mode-map)))
+ map)
+ "Local keymap used by Muse while on a link.")
+
+(defvar muse-keymap-property
+ (if (or (featurep 'xemacs)
+ (>= emacs-major-version 21))
+ 'keymap
+ 'local-map)
+ "The name of the keymap or local-map property.")
+
+(defsubst muse-link-properties (help-str &optional face)
+ "Determine text properties to use for a link."
+ (append (if face
+ (list 'face face 'mouse-face 'highlight 'muse-link t)
+ (list 'invisible 'muse 'intangible t))
+ (list 'help-echo help-str 'rear-nonsticky t
+ muse-keymap-property muse-mode-local-map)))
+
+(defun muse-link-face (link-name &optional explicit)
+ "Return the type of LINK-NAME as a face symbol.
+For EXPLICIT links, this is either a normal link or a bad-link
+face. For implicit links, it is either colored normally or
+ignored."
+ (save-match-data
+ (let ((link (if explicit
+ (muse-handle-explicit-link link-name)
+ (muse-handle-implicit-link link-name))))
+ (when link
+ (cond ((string-match muse-url-regexp link)
+ 'muse-link)
+ ((muse-file-remote-p link)
+ 'muse-link)
+ ((string-match muse-file-regexp link)
+ (when (string-match "/[^/]+#[^#./]+\\'" link)
+ ;; strip anchor from the end of a path
+ (setq link (substring link 0 (match-beginning 0))))
+ (if (file-exists-p link)
+ 'muse-link
+ 'muse-bad-link))
+ ((not (featurep 'muse-project))
+ 'muse-link)
+ (t
+ (if (string-match "#" link)
+ (setq link (substring link 0 (match-beginning 0))))
+ (if (or (and (muse-project-of-file)
+ (muse-project-page-file
+ link muse-current-project t))
+ (file-exists-p link))
+ 'muse-link
+ 'muse-bad-link)))))))
+
+(defun muse-colors-use-publishing-directory (link)
+ "Make LINK relative to the directory where we will publish the
+current file."
+ (let ((style (car (muse-project-applicable-styles
+ link (cddr (muse-project)))))
+ path)
+ (when (and style
+ (setq path (muse-style-element :path style)))
+ (expand-file-name link path))))
+
+(defun muse-colors-resolve-image-file (link)
+ "Determine if we can create images and see if the link is an image
+file."
+ (save-match-data
+ (and (or (fboundp 'create-image)
+ (fboundp 'make-glyph))
+ (not (string-match "\\`[uU][rR][lL]:" link))
+ (string-match muse-image-regexp link))))
+
+(defun muse-make-file-glyph (filename)
+ "Given a file name, return a newly-created image glyph.
+This is a hack for supporting inline images in XEmacs."
+ (let ((case-fold-search nil))
+ ;; Scan filename to determine image type
+ (when (fboundp 'make-glyph)
+ (save-match-data
+ (cond ((string-match "jpe?g" filename)
+ (make-glyph (vector 'jpeg :file filename) 'buffer))
+ ((string-match "gif" filename)
+ (make-glyph (vector 'gif :file filename) 'buffer))
+ ((string-match "png" filename)
+ (make-glyph (vector 'png :file filename) 'buffer)))))))
+
+(defun muse-colors-insert-image (link beg end invis-props)
+ "Create an image using create-image or make-glyph and insert it
+in place of an image link defined by BEG and END."
+ (setq link (expand-file-name link))
+ (let ((image-file (cond
+ ((eq muse-colors-inline-image-method 'default-directory)
+ link)
+ ((functionp muse-colors-inline-image-method)
+ (funcall muse-colors-inline-image-method link))))
+ glyph)
+ (when (stringp image-file)
+ (if (fboundp 'create-image)
+ ;; use create-image and display property
+ (let ((display-stuff (condition-case nil
+ (create-image image-file)
+ (error nil))))
+ (when display-stuff
+ (add-text-properties beg end (list 'display display-stuff))))
+ ;; use make-glyph and invisible property
+ (and (setq glyph (muse-make-file-glyph image-file))
+ (progn
+ (add-text-properties beg end invis-props)
+ (add-text-properties beg end (list
+ 'end-glyph glyph
+ 'help-echo link))))))))
+
+(defun muse-colors-explicit-link ()
+ "Color explicit links."
+ (when (and (eq ?\[ (char-after (match-beginning 0)))
+ (not (get-text-property (match-beginning 0) 'muse-comment))
+ (not (get-text-property (match-beginning 0) 'muse-directive)))
+ ;; remove flyspell overlays
+ (when (fboundp 'flyspell-unhighlight-at)
+ (let ((cur (match-beginning 0)))
+ (while (> (match-end 0) cur)
+ (flyspell-unhighlight-at cur)
+ (setq cur (1+ cur)))))
+ (let* ((unesc-link (muse-get-link))
+ (unesc-desc (muse-get-link-desc))
+ (link (muse-link-unescape unesc-link))
+ (desc (muse-link-unescape unesc-desc))
+ (props (muse-link-properties desc (muse-link-face link t)))
+ (invis-props (append props (muse-link-properties desc))))
+ ;; see if we should try and inline an image
+ (if (and muse-colors-inline-images
+ (or (muse-colors-resolve-image-file link)
+ (and desc
+ (muse-colors-resolve-image-file desc)
+ (setq link desc))))
+ ;; we found an image, so inline it
+ (muse-colors-insert-image
+ link
+ (match-beginning 0) (match-end 0) invis-props)
+ (if desc
+ (progn
+ ;; we put the normal face properties on the invisible
+ ;; portion too, since emacs sometimes will position
+ ;; the cursor on an intangible character
+ (add-text-properties (match-beginning 0)
+ (match-beginning 2) invis-props)
+ (add-text-properties (match-beginning 2) (match-end 2) props)
+ (add-text-properties (match-end 2) (match-end 0) invis-props)
+ ;; in case specials were escaped, cause the unescaped
+ ;; text to be displayed
+ (unless (string= desc unesc-desc)
+ (add-text-properties (match-beginning 2) (match-end 2)
+ (list 'display desc))))
+ (add-text-properties (match-beginning 0)
+ (match-beginning 1) invis-props)
+ (add-text-properties (match-beginning 1) (match-end 0) props)
+ (add-text-properties (match-end 1) (match-end 0) invis-props)
+ (unless (string= link unesc-link)
+ (add-text-properties (match-beginning 1) (match-end 1)
+ (list 'display link))))
+ (goto-char (match-end 0))
+ (add-text-properties
+ (match-beginning 0) (match-end 0)
+ (muse-link-properties (muse-match-string-no-properties 0)
+ (muse-link-face link t)))))))
+
+(defun muse-colors-implicit-link ()
+ "Color implicit links."
+ (unless (or (eq (get-text-property (match-beginning 0) 'invisible) 'muse)
+ (get-text-property (match-beginning 0) 'muse-comment)
+ (get-text-property (match-beginning 0) 'muse-directive)
+ (get-text-property (match-beginning 0) 'muse-no-implicit-link)
+ (eq (char-before (match-beginning 0)) ?\")
+ (eq (char-after (match-end 0)) ?\"))
+ ;; remove flyspell overlays
+ (when (fboundp 'flyspell-unhighlight-at)
+ (let ((cur (match-beginning 0)))
+ (while (> (match-end 0) cur)
+ (flyspell-unhighlight-at cur)
+ (setq cur (1+ cur)))))
+ ;; colorize link
+ (let ((link (muse-match-string-no-properties 0))
+ (face (muse-link-face (match-string 0))))
+ (when face
+ (add-text-properties (match-beginning 0) (match-end 0)
+ (muse-link-properties
+ (muse-match-string-no-properties 0) face))))))
+
+(defun muse-colors-title ()
+ "Color #title directives."
+ (let ((beg (+ 7 (match-beginning 0))))
+ (add-text-properties beg (muse-line-end-position) '(muse-directive t))
+ ;; colorize <lisp> tags in #title after other <lisp> tags have had a
+ ;; chance to run, so that we can have behavior that is consistent
+ ;; with how the document is published
+ (setq muse-colors-delayed-commands
+ (cons (list 'muse-colors-title-lisp beg (muse-line-end-position))
+ muse-colors-delayed-commands))))
+
+(defun muse-colors-title-lisp (beg end)
+ "Called after other highlighting is done for a region in order to handle
+<lisp> tags that exist in #title directives."
+ (save-restriction
+ (narrow-to-region beg end)
+ (goto-char (point-min))
+ (let ((muse-colors-inhibit-tags-in-directives nil)
+ (muse-colors-tags '(("lisp" t t nil muse-colors-lisp-tag))))
+ (while (re-search-forward muse-tag-regexp nil t)
+ (muse-colors-custom-tags))))
+ (add-text-properties beg end '(face muse-header-1)))
+
+(defun muse-colors-comment ()
+ "Color comments."
+ (add-text-properties (match-beginning 0) (muse-line-end-position)
+ (list 'face 'font-lock-comment-face
+ 'muse-comment t)))
+
+
+(provide 'muse-colors)
+
+;;; muse-colors.el ends here