From 57366f385a2f1f35bbe741d7542096db81368c72 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Thu, 17 Mar 2011 11:23:07 +0100 Subject: Big changes, last one before I wipe it all. * Added muse * Added graphviz-dot-mode * Remove all trailing whitespace on save. This is the last commit I'm going to do before throwing it all away again. --- emacs.d/elisp/muse/muse-colors.el | 1022 +++++++++++++++++++++++++++++++++++++ 1 file changed, 1022 insertions(+) create mode 100644 emacs.d/elisp/muse/muse-colors.el (limited to 'emacs.d/elisp/muse/muse-colors.el') 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 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 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 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 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 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 tags in #title after other 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 + 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 -- cgit v1.2.3-54-g00ecf