emacs-config/elpa/highlight-doxygen-20180829.1818/highlight-doxygen.el
2020-05-28 11:49:19 +02:00

1657 lines
55 KiB
EmacsLisp

;;; highlight-doxygen.el --- Highlight Doxygen comments
;; Copyright (C) 2016-2018 Anders Lindgren
;; Author: Anders Lindgren
;; Keywords: faces
;; Package-Version: 20180829.1818
;; Created: 2016-02-12
;; Version: 0.0.1
;; URL: https://github.com/Lindydancer/highlight-doxygen
;; This program 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 of the License, or
;; (at your option) any later version.
;; This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
;;; Commentary:
;; Advanced highlighting package for Doxygen comments.
;;
;; In addition to highlighting Doxygen commands and their arguments,
;; entire Doxygen comment are highlighted, making them stand out
;; compared to other comments. In addition, and code blocks are
;; highlighted according to the language they are written in.
;; Usage:
;;
;; This package provide two minor modes, `highlight-doxygen-mode' and
;; `highlight-doxygen-global-mode'.
;;
;; Can enable `highlight-doxygen-mode' from the hook of a mode.
;;
;; Alternatively, you can enable the minor mode for all major modes
;; specified in `highlight-doxygen-modes'. Typically, this is done by
;; placing the following in a suitable init file:
;;
;; (highlight-doxygen-global-mode 1)
;; What is highlighted:
;;
;; * The full Doxygen comment is highlighted with a different
;; background color, to make them stand out compared to normal code
;; other comments.
;;
;; * Doxygen commands and their arguments are highlighted. The
;; arguments are highlighted according to the signature of the
;; commands. For example, the argument to the `\a' command is
;; highlighted as a variable.
;;
;; * Code blocks are highlighted using the Emacs highlighting rules
;; for the language they are written in. In addition, the
;; background is changed to make the code block stand out.
;;
;; * Customization friendly. This package define a number of custom
;; faces that can be customized to fine tune the appearance if this
;; package. The default value of all defined faces inherit from
;; standard Emacs faces, which mean that customizations done by the
;; user or themes are automatically used.
;; Code blocks:
;;
;; A code block is specified using a pair of Doxygen commands like
;; `\code' and `\endcode' or `\dot' and `\enddot'.
;;
;; Code blocks are syntax highlighted using the major mode they are
;; written in. The major mode is selected as follows:
;;
;; * If the `\code{.ext}' construct is used, the major mode associated
;; with extension `.ext' is used.
;;
;; * For `\dot', `\msc', and `\startuml' is used, the extensions
;; `.dot', `.msc', and `.plantuml' are used, respectively.
;;
;; * For `\code' blocks that does not specify an extension, the major
;; mode of the buffer is used.
;;; Code:
;; Internal comments starts here.
;;
;; (Nothing below the "Code:" comment is included in the generated
;; README.md file.)
;; Known problems:
;;
;; * When there is space between two C-style Doxygen comments, it
;; should not be highlighted.
;;
;; > /*!
;; > * A comment
;; > */
;; > <-- Don't highlight here.
;; > /*!
;; > * A comment
;; > */
;;
;; * A \code without a matching \endcode should not pick up someone
;; elses \endcode, that would highlight too much.
;;
;; * Empty lines in Doxygen comments disrupt the "block" highlighting.
;;
;; > /* Title <-- The block starts on the "/".
;; > <-- Starts at the beginning of the line.
;; > */ <-- Starts at the space before the "*".
;;
;; * Empty lines in code disrupt the "code block" highlighting.
;;
;; > //! \code
;; > //! if (true)
;; > //! {
;; > //! <-- This line is not highlighted as code
;; > //! }
;; > //! \endcode
;;
;; * Verbatim blocks
;;
;; In Doxygen, everything between \verbatim and \endverbatim is
;; included, exactly as written. This include things like comment
;; start characters. Currently, verbatim blocks are highlighted like
;; code blocks.
;;
;; Also, when the \verbatim and \endverbatim is placed in different
;; comments, any code between them is included in the comment. This
;; package highlight one Doxygen comment block at a time; with this
;; system in place in it not possible to highlight blocks across
;; Doxygen comments.
;; Notes on individual commands:
;;
;; The Doxygen documentation says that "a", "e", and "em" are the
;; same. However, it also says that "a" should be used to highlights
;; parameters. This package highlight the argument of "a" as a
;; variable (to match the parameter list) and the argument to "e" and
;; "em" in a different way, by default in italics.
;;
;; The commands "sa" and "see" start a new paragraph that could
;; contains references and links. There are example code where the
;; paragraphs only contain one word, a reference, but in the general
;; case it's impossible to know that this really is the case.
;; Future ideas:
;;
;; This section contains "wild ideas" that aren't in the current
;; implementation plan.
;;
;; ## Hide "\a" in "\a var"?
;;
;; This would make the code more readable. However, it would also make
;; it easy to accidentally write badly formatted or too long lines.
;;
;; ## Highlight some commands differently
;;
;; For example, "\attention" "\warning" "\todo" "\bug" could be
;; highlighted in (a face inheriting from) the warning face.
;;
;; ## Support more commands
;;
;; - "\def foo" -- Highlight "foo" as a variable. "\def foo(x,y)" --
;; Highlight "foo" as a function and the parameters as variables.
;; Note: I failed to include \def:ed macros in the generated Doxygen
;; output, so it's hard to know if the commands are used as
;; intended.
;;
;; ## Highlight across lines
;;
;; Today, the argument to commands like "\a" must be place on the same
;; line as the command. However, Doxygen supports placing the
;; argument on the next line. Technically, one way to implement this
;; is to only match "\a" using the main regexp and replace the face
;; expression with a call to a function that highlights the next word
;; (possibly skipping comment start characters, stars etc, and then
;; return nil (so that font-lock doesn't add anther face on top of the
;; one we just used ourselves).
;;
;; ## Highlight HTML constructs
;;
;; Highlight HTML constructs like <tt>Multiple words</tt>. Tricky to
;; handle constructs that spam multiple lines and nested constructs.
;;
;; ## Clickable links
;;
;; Make it possible to click on "http"-like links. (Check if there
;; exist a package for this already.)
;;
;; ## Support for formats like JavaDoc
;;
;; Currently, this package only support Doxygen. However, a number of
;; similar formats exists, like JavaDoc. Maybe they should be
;; supported as well.
;;
;; ## Edit code block
;;
;; Add support to edit code blocks in a separate buffer, like in org
;; mode.
;; Implementation:
;;
;; Extra properties:
;;
;; In addition to faces, this package sets a number of text properties
;; on Doxygen comments.
;;
;; - `highlight-doxygen-code' -- Set on all lines in an actual code
;; block. This is used to prevent Doxygen font-lock rules from
;; being applied inside code blocks.
;;
;; - `highlight-doxygen-ignore' -- Set on all lines in a code block
;; from the code/verbatim Doxygen command to the endcode/endverbatim
;; command. This is used to prevent the markdown code rule from
;; inspecting the surrounding Doxygen commands, as they often are
;; heavily indented
;; Doxygen information:
;;
;; https://www.stack.nl/~dimitri/Doxygen/manual/commands.html
;;
;; A MarkDown-style code block needs four spaces more than the
;; surrounding text. When the indentation gets lets than four spaces,
;; the code block ends, even when in the middle of consecutive lines.
;;
;; From the Doxygen manual:
;;
;; > Some commands have one or more arguments. Each argument has a
;; > certain range:
;; >
;; > If <sharp> braces are used the argument is a single word.
;; >
;; > If (round) braces are used the argument extends until the end of
;; > the line on which the command was found.
;; >
;; > If {curly} braces are used the argument extends until the next
;; > paragraph. Paragraphs are delimited by a blank line or by a section
;; > indicator.
;; -------------------------------------------------------------------
;; Dependencies
;;
;; For the faces.
(require 'outline)
;; Don't warn for using these dynamically bound variables, see
;; `font-lock-extend-region-functions'.
(eval-when-compile
(defvar font-lock-beg)
(defvar font-lock-end))
;; -------------------------------------------------------------------
;; Variables, faces, and customization support
;;
(defgroup highlight-doxygen nil
"Highlight Doxygen comments."
:group 'faces)
(defface highlight-doxygen-comment
'((((background light)) :inherit font-lock-doc-face :background "grey95")
(((background dark)) :inherit font-lock-doc-face :background "grey30"))
"The face used for Doxygen comment blocks."
:group 'highlight-doxygen)
(defface highlight-doxygen-code-block
'((((background light)) :background "grey85")
(((background dark)) :background "grey40"))
"The face used to mark a code block."
:group 'highlight-doxygen)
(defface highlight-doxygen-command
'((t :inherit font-lock-constant-face))
"The face used to mark Doxygen commands."
:group 'highlight-doxygen)
(defcustom highlight-doxygen-modes
'(c-mode
c++-mode
objc-mode)
"List of major modes where Highlight Doxygen Global mode should be enabled.
The mode is enabled for buffers whose major mode is a member of
this list, or is derived from a member in the list."
:group 'highlight-doxygen
:type '(repeat symbol))
(defcustom highlight-doxygen-commend-start-regexp
"\\(/\\*\\(!\\|\\*[^*]\\)\\|//\\(!\\|/[^/\n]\\)\\)"
"Regexp matching the beginning of a Doxygen comment."
:group 'highlight-doxygen
:type 'regexp)
(defcustom highlight-doxygen-triple-slash-comment-regexp "///"
"Regexp to match triple slash comments."
:group 'highlight-doxygen
:type 'regexp)
(defcustom highlight-doxygen-ignore-triple-slash-comments nil
"When non-nil, triple slash comments are ignored."
:group 'highlight-doxygen
:type 'boolean)
;; -------------------------------------------------------------------
;; Helper functions.
;;
(defun highlight-doxygen-replace-in-sexp (new old sexp)
"Return a copy of SEXP where OLD has been replaced by NEW.
If OLD does not occur in SEXP, SEXP is returned."
(cond ((eq old sexp) new)
((consp sexp)
(let* ((lhs0 (car sexp))
(rhs0 (cdr sexp))
(lhs (highlight-doxygen-replace-in-sexp new old lhs0))
(rhs (highlight-doxygen-replace-in-sexp new old rhs0)))
(if (and (eq lhs lhs0)
(eq rhs rhs0))
sexp
(cons lhs rhs))))
(t sexp)))
;; -------------------------------------------------------------------
;; Code block highlighter.
;;
(defun highlight-doxygen-next-property-change (pos property)
"Next position after POS where PROPERTY change.
If POS is nil, also include `point-min' in the search.
If last character contains the property, return `point-max'."
(if (equal pos (point-max))
;; Last search returned `point-max'. There is no more to search
;; for.
nil
(if (and (null pos)
(get-text-property (point-min) property))
;; `pos' is `nil' and the character at `point-min' contains
;; the property, return `point-min'.
(point-min)
(unless pos
;; Start from the beginning.
(setq pos (point-min)))
;; Do a normal search. Compensate for that
;; `next-single-property-change' does not include the end of the
;; buffer, even when the property reach it.
(let ((res (next-single-property-change pos property)))
(if (and (not res) ; No more found.
(not (equal pos (point-max))) ; Not already at the end.
(not (equal (point-min) (point-max))) ; Not an empty buffer.
(get-text-property (- (point-max) 1) property))
;; If the property goes all the way to the end of the
;; buffer, return `point-max'.
(point-max)
res)))))
;; TODO: Check if face properties on newline are handled properly.
(defun highlight-doxygen-code-block (start end &optional mode)
"Highlight block between START and END as code.
MODE determined which major mode should be used to highlight the
block.
When MODE is a string, it should be on the form of a file
extension, and the major mode associated with the file extension
is used. When a function, that function is called to set the
major mode. When nil, the block is not highlighted."
;; ----------
;; Mark the code block so that the other rules doesn't overwrite it.
;; TODO: As other rules use this, move it to the function that
;; handle code rule.
(add-text-properties start end '(highlight-doxygen-code t))
;; ----------
;; Find start column
(let (column)
(save-excursion
(goto-char start)
(while (< (point) end)
(let ((indentation (highlight-doxygen-current-indentation)))
(when indentation
(when (or (null column)
(< indentation column))
(setq column indentation))))
(forward-line)))
;; ----------
(save-excursion
(goto-char start)
(let ((buf (get-buffer-create " highlight-doxygen"))
(src-buf (current-buffer)))
;; ----------
;; Copy code block to temp buffer, ignoring anything to the left
;; of the starting column of the code block.
(with-current-buffer buf
(delete-region (point-min) (point-max))
;; Ensure temp buffers doesn't keep major mode over calls.
(fundamental-mode))
(while (< (point) end)
;; Go to column, include a tab if it spans the intended column.
(move-to-column column)
(when (> (current-column) column)
(backward-char))
(let ((line (buffer-substring-no-properties
(point)
(min end (line-end-position))))
(origin (point)))
(with-current-buffer buf
(insert line)
(set-text-properties
(line-beginning-position)
(line-end-position)
(list 'highlight-doxygen origin))
(insert "\n")))
(forward-line))
;; ----------
;; Highlight the code block.
(save-excursion
(goto-char start)
(while (< (point) end)
(move-to-column column)
(when (> (current-column) column)
(backward-char))
(unless (eolp)
(font-lock-prepend-text-property
(point)
(min end (line-beginning-position 2))
'face
'highlight-doxygen-code-block))
(forward-line)))
;; ----------
;; Copy syntax highlighting from temp to original buffer.
(with-current-buffer buf
(when (ignore-errors
(cond ((stringp mode)
(let ((buffer-file-name
(concat default-directory
"dummy" mode)))
(set-auto-mode))
t)
((functionp mode)
(funcall mode)
t)
;; Don't highlight.
(t nil)))
(unless (eq major-mode 'fundamental-mode)
(ignore-errors
(font-lock-fontify-region (point-min) (point-max))))
(goto-char (point-min))
(while (not (eobp))
(let ((origin (get-text-property (point) 'highlight-doxygen))
next)
(while (and (not (eolp))
(setq next (next-single-property-change
(point) 'face
nil (line-end-position))))
(let ((face (get-text-property (point) 'face)))
(when face
;; Without this, code block containing comments
;; will look half done when rendered in a code
;; block.
(dolist (old '(font-lock-comment-face
font-lock-comment-delimiter-face
font-lock-doc-face
highlight-doxygen-comment
default))
(setq face (highlight-doxygen-replace-in-sexp
'highlight-doxygen-code-block
old
face)))
(font-lock-prepend-text-property
(+ origin (- (point) (line-beginning-position)))
(+ origin (- next (line-beginning-position)))
'face
face
src-buf)))
(goto-char next)))
(forward-line))))))))
(defun highlight-doxygen-forward-to-indentation ()
"Move point to first text after comment starter in current line.
Return non-nil if the line is not empty, it only contains
whitespace and comment start characters."
(skip-syntax-forward " ")
(cond ((looking-at "\\(/\\*\\(!\\|\\*[^*]\\)\\|//\\(!\\|/[^/]\\)\\)")
(goto-char (+ (match-beginning 0) 3)))
((eq (following-char) ?*)
(forward-char)))
(skip-syntax-forward " ")
(not (eolp)))
(defun highlight-doxygen-current-indentation ()
"The current indentation, or nil if line is empty.
\"Empty\", in this context mean that it only contains whitespace
and comment start characters."
(save-excursion
(and (highlight-doxygen-forward-to-indentation)
(current-column))))
(defun highlight-doxygen-forward-to-paragraph-start (limit)
"Move forward to start of Doxygen comment paragraph.
Empty lines and highlighted code blocks are skipped.
Return indentation for first line in paragraph, or nil if LIMIT is reached."
(let ((res nil))
(while
(if (>= (point) limit)
nil
(setq res (highlight-doxygen-current-indentation))
(not res))
(forward-line))
res))
;; Doxygen treats a block as a code block when it's indented four
;; steps more than the preceding paragraph.
;;
;; Note that text lines that step up the indentation doesn't qualify.
;;
;; Point is at the beginning of a Doxygen comment or after another
;; code block.
(defun highlight-doxygen-find-and-highlight-markdown-code-block (limit)
"Skip to next paragraph and maybe highlight a MarkDown code block.
Move point to end of the current paragraph. If the paragraph is
followed by a MarkDown code block (i.e. a block indented four
spaces more than the paragraph), highlight the code block and
move point to the paragraph after it.
Do not search past LIMIT.
Return non-nil if a paragraph was found.
This function is intended to be used in a font-lock keyword."
(let ((paragraph-indentation
(highlight-doxygen-forward-to-paragraph-start limit)))
(when paragraph-indentation
;; Skip to end of paragraph.
(forward-line)
(while (and (< (point) limit)
(or
(get-text-property (point) 'highlight-doxygen-ignore)
(let ((indentation
(highlight-doxygen-current-indentation)))
(if indentation
(progn
;; Record the last line of the paragraph.
(setq paragraph-indentation indentation)
t)
nil))))
(forward-line))
;; Skip to next non-empty line.
(while (and (< (point) limit)
(not (highlight-doxygen-current-indentation)))
(forward-line))
;; Skip code block (if present).
(let ((start (point))
(least-indentation nil))
(while (and
(< (point) limit)
;; Stop at explicit code blocks (it will be skipped by
;; `highlight-doxygen-forward-to-paragraph-start' the
;; next time this function is called).
(not (get-text-property (point) 'highlight-doxygen-ignore))
(let ((indentation (highlight-doxygen-current-indentation)))
(if indentation
(if (< indentation (+ paragraph-indentation 4))
;; Indentation no longer least four spaces, stop.
nil
;; Continue skipping.
(setq least-indentation
(if least-indentation
(min least-indentation
indentation)
indentation))
t)
;; Empty line in block.
t)))
(forward-line))
(if least-indentation
(highlight-doxygen-code-block
(+ start least-indentation)
(point))
(goto-char start))))
;; By returning a non-nil value (regardless if a code block is
;; present), this function is called again when used in a
;; font-lock rule.
paragraph-indentation))
(defcustom highlight-doxygen-code-block-commands
'(("code" "endcode")
("dot" "enddot")
("msc" "endmsc")
("startuml" "enduml")
("verbatim" "endverbatim"))
"List of Doxygen commands that start a code block.
Each entry in the list is a list on the form (START-COMMAND END-COMMAND)."
:group 'highlight-doxygen
:type 'sexp)
(defcustom highlight-doxygen-block-major-mode-alist
'(("code" . t)
("dot" . ".dot")
("msc" . ".msc")
("startuml" . ".plantuml")
("verbatim" . nil))
"Alist used to determine major mode for Doxygen commands.
The key (car part) is a Doxygen command. The value (cdr part)
can be one of the following:
- t -- the buffer is checked for the {.ext} construct
- nil -- No special major mode is used.
- A string -- An file extension
- A symbol -- A major mode."
:group 'highlight-doxygen
:type 'sexp)
(defun highlight-doxygen-block-major-mode (command)
"Return the major mode or extension that should be used for block.
When a major mode is returned, it is returned as a symbol. When
it's an extension, it is returned as a string.
Return nil when no suitable major mode is found.
COMMAND is the Doxygen command of the block. The variable
`highlight-doxygen-block-major-mode-alist' is used to determine
the Emacs major mode should be used for highlighting.
Point is initially placed after the command. If the Doxygen
command supports the `{.ext}' construct, the point is moved to
the end of the construct."
(let ((entry (assoc command highlight-doxygen-block-major-mode-alist)))
(and entry
(let ((value (cdr entry)))
(cond ((null value)
nil)
((eq value t)
(save-match-data
(if (looking-at "{\\(\\.\\sw+\\)}")
(progn
(goto-char (match-end 0))
(match-string 1))
major-mode)))
(t
value))))))
(defun highlight-doxygen-find-and-highlight-keywords-code-block (limit)
"Highlight next code block within `code' or `verbatim' Doxygen commands.
Do not search past LIMIT."
(let ((start-regexp
(concat
"[\\@]\\("
(regexp-opt (mapcar (lambda (e)
(nth 0 e))
highlight-doxygen-code-block-commands))
"\\)")))
(if (re-search-forward start-regexp limit t)
(let ((mode (highlight-doxygen-block-major-mode (match-string 1)))
(keyword-start (match-beginning 0)))
(skip-syntax-forward " ")
(when (and (eolp)
(< (point) limit))
(forward-line)
(while (and (< (point) limit)
(not (highlight-doxygen-forward-to-indentation)))
(forward-line)))
(let ((start (point))
(end-regexp
(concat
"[\\@]"
(regexp-opt (mapcar (lambda (e)
(nth 1 e))
highlight-doxygen-code-block-commands))
"\\_>")))
(if (re-search-forward end-regexp limit t)
(save-excursion
(let ((end (match-beginning 0))
(keyword-end (match-beginning 0)))
;; Backup to last line containing anything
(beginning-of-line)
(highlight-doxygen-forward-to-indentation)
(when (eq (point) end)
(while
(progn
(forward-line -1)
(setq end (line-beginning-position 2))
(and
(< start (point))
(null
(highlight-doxygen-forward-to-indentation))))))
(highlight-doxygen-code-block start end mode)
;; Mark the code block (including code/endcode) to
;; ensure that the markdown code block highlighter
;; ignores it.
(add-text-properties
(save-excursion
(goto-char keyword-start)
(line-beginning-position))
(save-excursion
(goto-char keyword-end)
(line-end-position))
'(highlight-doxygen-ignore t))
t))
nil)))
nil)))
(defun highlight-doxygen-highlight-link-object ()
"Highlight reference or filename following point, if any."
(skip-syntax-forward "-")
(let ((start (point))
(end (point))
(limit (line-end-position)))
;; Match file names and references.
;;
;; Example:
;;
;; myname.h
;; myfunc()
;; myfunc(unsigned long) <- Note the space
(while (and (not (looking-at "\\s-"))
(condition-case nil
(progn
(forward-sexp)
(if (<= (point) limit)
(progn
(setq end (point))
t)
nil))
(error nil))))
(when (< start end)
;; TODO: How do we tell file names apart from references?
(if (string-match "\\." (buffer-substring start end))
;; File name
(font-lock-prepend-text-property
start end 'face 'font-lock-constant-face)
;; Reference
(highlight-doxygen-code-block start end major-mode)))))
;; -------------------------------------------------------------------
;; Font lock keywords
;;
;; --------------------
;; bold
;;
(defcustom highlight-doxygen-bold-commands
'("b")
"List of Doxygen commands that make their argument bold."
:group 'highlight-doxygen
:type '(repeat string))
;; Note: `:inherit bold' is used over `:weight bold', to minimize the
;; number of faces a user or a theme would have to customize.
(defface highlight-doxygen-bold
'((t :inherit bold))
"The face used to make text bold."
:group 'highlight-doxygen)
;; --------------------
;; code
;;
(defcustom highlight-doxygen-code-commands
'("c" "retval")
"List of Doxygen commands that make their argument code."
:group 'highlight-doxygen
:type '(repeat string))
(defface highlight-doxygen-code
'((t :inherit font-lock-constant-face))
"The face used to highlight things as code within Doxygen comments.
This is not used for code blocks."
:group 'highlight-doxygen)
;; --------------------
;; emphasize
;;
(defcustom highlight-doxygen-emphasize-commands
'("e" "em")
"List of Doxygen commands that emphasize their argument."
:group 'highlight-doxygen
:type '(repeat string))
;; Note: `:inherit italic' is used over `:slant italic', to minimize
;; the number of faces a user or a theme would have to customize.
(defface highlight-doxygen-emphasize
'((t :inherit italic))
"The face used to make text emphasized."
:group 'highlight-doxygen)
;; --------------------
;; exception
;;
(defcustom highlight-doxygen-exception-commands
'("exception" "idlexcept" "throw" "throws")
"List of Doxygen commands that take an exception argument."
:group 'highlight-doxygen
:type '(repeat string))
;; Note: This mimics C++ mode.
(defface highlight-doxygen-exception
'((t :inherit font-lock-type-face))
"The face for exceptions in Doxygen comments."
:group 'highlight-doxygen)
;; --------------------
;; namespace
;;
(defcustom highlight-doxygen-namespace-commands
'("namespace")
"List of Doxygen commands that take a namespace argument."
:group 'highlight-doxygen
:type '(repeat string))
;; Note: This mimics C++ mode.
(defface highlight-doxygen-namespace
'((t :inherit font-lock-constant-face))
"The face for namespaces in Doxygen comments."
:group 'highlight-doxygen)
;; --------------------
;; Class
;;
(defcustom highlight-doxygen-qualified-type-commands
'("class" "enum" "extends" "implements" "interface" "memberof"
"protocol" "relates" "related" "relatesalso" "relatedalso"
"struct" "union")
"List of Doxygen commands that take a \"class\" argument."
:group 'highlight-doxygen
:type '(repeat string))
(defface highlight-doxygen-type
'((t :inherit font-lock-type-face))
"The face used to highlight class arguments."
:group 'highlight-doxygen)
;; --------------------
;; Group
;;
;; TODO: The `ingroup' commands can take multiple groups as
;; arguments. Since this is rule itself is included in a anchored
;; match, it's not possible to handle this using anchored matches, so
;; it must be done in elisp.
(defcustom highlight-doxygen-group-commands
'("addtogroup" "defgroup" "ingroup" "weakgroup")
"List of Doxygen commands that take a \"group\" argument."
:group 'highlight-doxygen
:type '(repeat string))
(defface highlight-doxygen-group
'((t :inherit highlight))
"The face used to highlight group arguments."
:group 'highlight-doxygen)
;; --------------------
;; File names
;;
(defface highlight-doxygen-filename
'((t :inherit highlight))
"The face used to highlight filename arguments."
:group 'highlight-doxygen)
(defcustom highlight-doxygen-filename-commands
'("dir" "dontinclude" "example" "file" "htmlinclude" "include"
"includedoc" "includelineno" "latexinclude" "verbinclude"
;; Note: also takes "( block_id )"
"snippet" "snippetdoc" "snippetlineno"
;; Note: Also takes "["caption"] [<sizeindication>=<size>]"
"diafile" "dotfile" "mscfile"
;; Note: Also takes "[<header-name>]":
"headerfile")
"List of Doxygen commands that take a filename argument."
:group 'highlight-doxygen
:type '(repeat string))
;; --------------------
;; Reference (named page or anchor)
;;
(defcustom highlight-doxygen-reference-commands
'("ref" "refitem" "xrefitem")
"List of Doxygen commands that take a reference argument."
:group 'highlight-doxygen
:type '(repeat string))
;; --------------------
;; Section label
;;
(defcustom highlight-doxygen-section-label-commands
'("cond" "if" "ifnot" "elseif")
"List of Doxygen commands that take a \"section-label\" argument."
:group 'highlight-doxygen
:type '(repeat string))
(defface highlight-doxygen-section-label
'((t :inherit font-lock-type-face))
"The face used to highlight section label arguments."
:group 'highlight-doxygen)
;; --------------------
;; Variables
;;
(defcustom highlight-doxygen-variable-commands
'("a" "p" "tparam")
"List of Doxygen commands that take a variable argument."
:group 'highlight-doxygen
:type '(repeat string))
(defcustom highlight-doxygen-variable-with-dir-commands
'("param")
"List of Doxygen commands that take an optional dir and a variable argument."
:group 'highlight-doxygen
:type '(repeat string))
(defface highlight-doxygen-direction
'((t :inherit font-lock-builtin-face))
"The face used to highlight parameter direction."
:group 'highlight-doxygen)
(defface highlight-doxygen-variable
'((t :inherit font-lock-variable-name-face))
"The face used to highlight variables in Doxygen comments."
:group 'highlight-doxygen)
;; --------------------
;; Line of code
;;
(defcustom highlight-doxygen-code-line-commands
'("fn" "var" "typedef" "property" "overload")
"List of Doxygen commands that take a line of code as argument."
:group 'highlight-doxygen
:type '(repeat string))
;; --------------------
;; Link object (member, type, page, filename, etc.)
;;
(defcustom highlight-doxygen-link-object-commands
'("copybrief" "copydetails" "copydoc")
"List of Doxygen commands that take a line of code or file name as argument."
:group 'highlight-doxygen
:type '(repeat string))
;; --------------------
;; Links
;;
(defface highlight-doxygen-link
'((t :inherit link))
"The face used to highlight links (URL:s) in Doxygen comments."
:group 'highlight-doxygen)
;; --------------------
;; Titles
;;
;; mainpage -- heading-1
;; section -- heading-2
;; subsection -- heading-3
;; subsubsection -- heading-4
;; paragraph -- heading-5
;; page -- heading-2
;; subpage -- heading-3
(defface highlight-doxygen-label
'((t :inherit font-lock-type-face))
"The face used to highlight level 1 headings."
:group 'highlight-doxygen)
(defface highlight-doxygen-heading-1
'((t :inherit outline-1))
"The face used to highlight level 1 headings."
:group 'highlight-doxygen)
(defface highlight-doxygen-heading-2
'((t :inherit outline-2))
"The face used to highlight level 2 headings."
:group 'highlight-doxygen)
(defface highlight-doxygen-heading-3
'((t :inherit outline-3))
"The face used to highlight level 3 headings."
:group 'highlight-doxygen)
(defface highlight-doxygen-heading-4
'((t :inherit outline-4))
"The face used to highlight level 4 headings."
:group 'highlight-doxygen)
(defface highlight-doxygen-heading-5
'((t :inherit outline-5))
"The face used to highlight level 5 headings."
:group 'highlight-doxygen)
(defcustom highlight-doxygen-title-commands-alist
'((("mainpage") . highlight-doxygen-heading-1)
;; Note: Doxygen use the same font size for "par" as
;; subsubsections.
(("par" "vhdlflow") . highlight-doxygen-heading-4))
"Alist from list of Doxygen commands to faces.
The Doxygen commands should take one argument, a title."
:group 'highlight-doxygen
:type 'sexp)
;; Note: According to Doxygen, "subpage" requires that the title is
;; placed within quotes whereas the title of the other commands should
;; not use quotes. The highlighting rules generated from this
;; highlight the rest of the line as a title, regardless of quoting
;; style.
(defcustom highlight-doxygen-name-title-commands-alist
'((("section" "page") . highlight-doxygen-heading-2)
(("subsection" "subpage") . highlight-doxygen-heading-3)
(("subsubsection") . highlight-doxygen-heading-4)
(("paragraph") . highlight-doxygen-heading-5))
"Alist from list of Doxygen commands to faces.
The Doxygen commands should take two arguments, a name and a
title."
:group 'highlight-doxygen
:type 'sexp)
;; --------------------
;; The font-lock rules
;;
(defvar highlight-doxygen-comment-start-position nil)
(make-variable-buffer-local 'highlight-doxygen-comment-start-position)
(defvar highlight-doxygen-comment-end-position nil)
(make-variable-buffer-local 'highlight-doxygen-comment-end-position)
(defvar highlight-doxygen-start-column nil)
(make-variable-buffer-local 'highlight-doxygen-start-column)
;; Note: In case a face has a corresponding variable (like
;; `font-lock-constant-face', the face expression doesn't quote the
;; name of the face. Technically, the face expression refers to the
;; variable. This allows users to customize the face be changing the
;; variable. However, this technique is deprecated in favour of using
;; `face-remapping-alist'.
(defun highlight-doxygen-anchored-keywords-template ()
"List of font-lock keywords that will be converted to anchored submatches.
The MATCHER will be wrapped in a call to
`highlight-doxygen-forward-search' and pre and post match forms
will be added.
Note that these rules can't contain anchored rules themselves."
(let ((title-rules '()))
(dolist (pair highlight-doxygen-title-commands-alist)
(let ((commands (car pair))
(face (cdr pair)))
(push `(,(concat "[\\@]\\_<"
(regexp-opt commands)
"\\s-+"
"\\(.*\\)")
(1 (quote ,face) prepend))
title-rules)))
(dolist (pair highlight-doxygen-name-title-commands-alist)
(let ((commands (car pair))
(face (cdr pair)))
(push `(,(concat "[\\@]\\_<"
(regexp-opt commands)
"\\s-+"
"\\_<\\(\\sw+\\)"
"\\(\\s-+"
"\\(.*\\)\\)?")
(1 'highlight-doxygen-label prepend)
(2 (quote ,face) prepend t))
title-rules)))
(append
`(
;; --------------------
;; Highlight every line in the Doxygen block.
;;
;; Unlike plain comment highlighting, make the highlighting
;; follow the indentation of the Doxygen comment.
(highlight-doxygen-match-comment-line
(0 'highlight-doxygen-comment prepend))
;; --------------------
;; Explicit code blocks
(highlight-doxygen-find-and-highlight-keywords-code-block)
;; --------------------
;; Implicit (indented) code blocks
(highlight-doxygen-find-and-highlight-markdown-code-block)
;; --------------------
;; Doxygen command.
(,(concat "[\\@]"
"\\_<\\([a-z]+\\)\\_>")
(1 'highlight-doxygen-command prepend))
;; ----------------------------------------
;; Inline constructs.
;; --------------------
;; Type name
(highlight-doxygen-match-camel-case
(1 font-lock-type-face prepend))
;; --------------------
;; Qualified class name
("\\_<\\(\\sw+\\)\\(::\\|#\\)"
(1 font-lock-type-face prepend))
;; --------------------
;; Function name
("\\_<\\(\\(\\sw\\)+()\\)"
(1 font-lock-function-name-face prepend))
;; --------------------
;; Links (URI:s). See RFC 3986, chapter 3.
("\\_<\\([a-zA-Z][-a-zA-Z0-9+.]*://[^ \t\n]*\\)"
(1 'highlight-doxygen-link prepend)))
title-rules
`(
;; ------------------------------
;; Various command signatures.
;;
;; --------------------
;; bold
(,(concat "[\\@]\\_<"
(regexp-opt highlight-doxygen-bold-commands)
"\\s-+"
"\\_<\\(\\sw+\\)")
(1 'highlight-doxygen-bold prepend))
;; --------------------
;; code
(,(concat "[\\@]\\_<"
(regexp-opt highlight-doxygen-code-commands)
"\\s-+"
"\\_<\\(\\sw+\\)")
(1 'highlight-doxygen-code prepend))
;; --------------------
;; emphasize
(,(concat "[\\@]\\_<"
(regexp-opt highlight-doxygen-emphasize-commands)
"\\s-+"
"\\_<\\(\\sw+\\)")
(1 'highlight-doxygen-emphasize prepend))
;; --------------------
;; Type name
(,(concat "[\\@]\\_<"
(regexp-opt highlight-doxygen-qualified-type-commands)
"\\s-+"
;; Skip qualifiers.
"\\_<\\(?:\\sw+\\(?:::\\|#\\)\\)*"
"\\_<\\(\\sw+\\)")
(1 'highlight-doxygen-type prepend))
;; --------------------
;; exception
(,(concat "[\\@]\\_<"
(regexp-opt highlight-doxygen-exception-commands)
"\\s-+"
;; Skip qualifiers.
"\\_<\\(?:\\sw+\\(?:::\\|#\\)\\)*"
"\\(\\sw+\\)")
(1 'highlight-doxygen-exception prepend))
;; --------------------
;; namespace
(,(concat "[\\@]\\_<"
(regexp-opt highlight-doxygen-namespace-commands)
"\\s-+"
;; Skip qualifiers.
"\\_<\\(?:\\sw+\\(?:::\\|#\\)\\)*"
"\\_<\\(\\sw+\\)")
(1 'highlight-doxygen-namespace prepend))
;; --------------------
;; Group name
(,(concat "[\\@]\\_<"
(regexp-opt highlight-doxygen-group-commands)
"\\s-+"
"\\_<\\(\\sw+\\)")
(1 'highlight-doxygen-group prepend))
;; --------------------
;; File name
(,(concat "[\\@]\\_<"
(regexp-opt highlight-doxygen-filename-commands)
"\\s-+"
"\\_<\\([a-zA-Z0-9_:/\\.]+\\)")
(1 'highlight-doxygen-filename prepend))
;; --------------------
;; Reference
;; Note: The Doxygen documentation doesn't specify the format
;; of a reference, this code use a combination of word
;; characters, symbol characters, and punctuation
;; characters. Another approach would be to match every
;; character except whitespace. Unfortunately, "\\S-" might
;; match newlines, so the search must be restricted to the end
;; of the line that contains the Doxygen command.
(,(concat "[\\@]\\_<"
(regexp-opt highlight-doxygen-reference-commands)
"\\s-+"
"\\(\\(\\sw\\|\\s_\\|\\s.\\)+\\)")
(1 'highlight-doxygen-link prepend))
;; --------------------
;; section-label (`if' and `elseif' etc.)
;; TODO: The section label can be a complex expression like
;; "(TEST1 && !TEST2). Since this is rule itself is included in a
;; anchored match, it's not possible to handle this using anchored
;; matches, so it must be done in elisp.
(,(concat "[\\@]\\_<"
(regexp-opt highlight-doxygen-section-label-commands)
"\\s-+"
"\\_<\\(\\sw+\\)")
(1 'highlight-doxygen-section-label prepend))
;; --------------------
;; Variable
(,(concat "[\\@]\\_<"
(regexp-opt highlight-doxygen-variable-commands)
"\\s-+"
"\\_<\\(\\sw+\\)")
(1 'highlight-doxygen-variable prepend))
;; --------------------
;; Variable with direction
(,(concat "[\\@]\\_<"
(regexp-opt highlight-doxygen-variable-with-dir-commands)
"\\_>"
"\\s-*"
"\\(?:\\["
"\\(?:\\(in\\)\\|\\(out\\)\\|\\(in\\),\\(out\\)\\)"
"\\]\\)?"
"\\s-*"
"\\(\\_<\\sw+\\)?")
(1 'highlight-doxygen-direction prepend t) ; in
(2 'highlight-doxygen-direction prepend t) ; out
(3 'highlight-doxygen-direction prepend t) ; in (part of in,out)
(4 'highlight-doxygen-direction prepend t) ; out (part of in,out)
(5 'highlight-doxygen-variable prepend t))
;; --------------------
;; Line of code
(,(concat "[\\@]\\_<"
(regexp-opt highlight-doxygen-code-line-commands)
"\\s-+\\(.*\\)$")
(0 (progn
(highlight-doxygen-code-block
(match-beginning 1)
(match-end 1)
major-mode)
nil)))
;; --------------------
;; Reference or file name
(,(concat "[\\@]\\_<"
(regexp-opt highlight-doxygen-link-object-commands)
"\\_>")
(0 (progn
;; This will apply suitable highlighting to whatever is
;; after the command.
(highlight-doxygen-highlight-link-object)
nil)))
;; --------------------
;; Highlight "`foo`". Note that in Doxygen a quote cancels a
;; backquote.
;;
;; TODO: Multi-line support.
("`\\([^\n`']+\\)`"
(1 (progn
(goto-char (match-end 0))
font-lock-constant-face)
prepend))))))
(defun highlight-doxygen-forward-search (matcher limit)
"Search for MATCHER but skip Doxygen code blocks.
If MATCHER is a string `re-search-forward' is used, otherwise it
is called as a function.
Do not search past LIMIT."
(let (res)
(while (let ((old-point (point)))
(setq res (and (if (stringp matcher)
(re-search-forward matcher limit t)
(funcall matcher limit))
;; Without this, if the matcher function
;; doesn't move the point, Emacs hangs.
(< old-point (point))))
(and res
(get-text-property (point) 'highlight-doxygen-code))))
res))
(defun highlight-doxygen--pre-match-form ()
"Prepare for submatches in an anchored font-lock rule.
Move point to start of current Doxygen comment and return the
end, making it the region the sub matcher will be applied to."
;; Font-lock moves point one position in an attempt to avoid
;; infinite searches after matching the main matcher. We need
;; to move the point back to the start of the comment to make
;; the block highlighting work properly.
(goto-char highlight-doxygen-comment-start-position)
;; Search limit
highlight-doxygen-comment-end-position)
(defun highlight-doxygen--post-match-form ()
"Wind up an anchored font-lock rule."
;; TODO: If this is nil, font-lock work but font-lock-studio
;; hangs. Find out why!
(goto-char highlight-doxygen-comment-end-position))
(defun highlight-doxygen-compose-font-lock-keywords ()
"Construct the font-lock keywords for highlighting Doxygen comments."
(let (subrules '())
(dolist (template (highlight-doxygen-anchored-keywords-template))
(let ((expr (nth 0 template)))
(unless (stringp expr)
(setq expr (list 'function expr))) ; Same as #'
(push `((lambda (limit)
(highlight-doxygen-forward-search ,expr limit))
(highlight-doxygen--pre-match-form)
(highlight-doxygen--post-match-form)
,@(cdr template))
subrules)))
`((highlight-doxygen-match-comment ,@(nreverse subrules)))))
;; -------------------------------------------------------------------
;; Comment matcher.
;;
(defun highlight-doxygen-next-comment (limit)
"Search for next Doxygen comment.
Stop search at LIMIT. If a Doxygen comment is found, move point
and return non-nil. Otherwise nil is returned (point may be
moved)."
(let (res)
(while
(and
(setq res (re-search-forward
highlight-doxygen-commend-start-regexp
limit t))
;; Continue looping if:
(or (and
highlight-doxygen-ignore-triple-slash-comments
(save-excursion
(goto-char (match-beginning 0))
(looking-at highlight-doxygen-triple-slash-comment-regexp)))
(let ((state (syntax-ppss)))
(or
;; Not in comment?
(not (nth 4 state))
;; At the start of the comment?
(not (eq (match-beginning 0) (nth 8 state))))))))
(when res
(goto-char (match-beginning 0)))
res))
(defun highlight-doxygen-move-end-of-comment ()
"Move point to end of Doxygen comment.
Treat consecutive Doxygen comments like one."
(let ((end (point)))
(while
(if (forward-comment 1)
(progn
(setq end (point))
(skip-chars-forward " \t\n")
(looking-at highlight-doxygen-commend-start-regexp))
;; Guard against being enabled in modes where the comment
;; syntax doesn't match
;; `highlight-doxygen-commend-start-regexp'.
(setq end (line-end-position))
nil))
(goto-char end)
(when (and (eolp)
(not (bolp)))
(forward-line))))
(defun highlight-doxygen-match-comment (limit)
"Find next Doxygen comment.
Do not search past LIMIT.
Set `highlight-doxygen-comment-end-position' to end of Doxygen comment."
(let ((res (highlight-doxygen-next-comment limit)))
(when res
(setq highlight-doxygen-comment-start-position (point))
(setq highlight-doxygen-comment-end-position
;; Respect `limit'.
;;
;; In normal operation, the refontified region is always
;; extended to include the full comment. However, when
;; using font-lock studio (a debugger for font-lock
;; keywords) the user may use a smaller region.
(min
(save-excursion
(highlight-doxygen-move-end-of-comment)
(point))
limit))
(setq highlight-doxygen-start-column (current-column)))
res))
(defun highlight-doxygen-match-comment-line (limit)
"Match a single Doxygen comment line.
Do not search past LIMIT."
(while (and (< (current-column) highlight-doxygen-start-column)
(not (eolp))
(memq (following-char) '(?\s ?\t)))
(forward-char))
(if (or (and (eolp)
(not (bolp)))
(> (point) limit))
nil
(set-match-data (list (point) (min (save-excursion
(forward-line)
(point))
limit)))
(forward-line)
t))
(defun highlight-doxygen-match-camel-case (limit)
"Search for next type, which is a CamelCase word.
Do not search past LIMIT.
Constructs like `CamelCase(' are ignored, as they are assumed to
be function calls.
Constructs like `CamelCase.h' are ignores, as they look like file
names."
(let (res)
(while (and (setq res (re-search-forward
(concat "\\_<\\("
;; Match a word written using
;; CamelCase, starting with a
;; capital letter.
;;
;; TODO: Allow "_"?
"[A-Z]+[a-zA-Z0-9]*[a-z]+[a-zA-Z0-9]*"
"[A-Z][a-zA-Z0-9]+"
"\\)\\_>")
limit t))
(or (save-excursion
;; This is a function call, ignore.
(skip-chars-forward " \t")
(eq (following-char) ?\( ))
(save-match-data
;; This is a file extension, ignore.
(looking-at "\\.[a-zA-Z]+\\_>")))))
res))
;; -------------------------------------------------------------------
;; Region extender.
;;
(defun highlight-doxygen-inside-special-comment ()
"Return start of Doxygen-style comment, or nil.
Treat consecutive line comments like one block."
(save-excursion
(let ((res nil))
(while
(progn
(skip-chars-backward " \t")
(when (and (not (bobp))
(bolp))
(backward-char))
(let ((state (syntax-ppss)))
(if (nth 4 state) ; Comment
(progn
(goto-char (nth 8 state))
(if (looking-at highlight-doxygen-commend-start-regexp)
(progn
(setq res (point))
t)
nil))
nil))))
res)))
(defun highlight-doxygen-extend-region-full-comment ()
"Extend font-lock region to include the full Doxygen comment."
(save-excursion
(let ((res nil))
(goto-char font-lock-beg)
(let ((start (highlight-doxygen-inside-special-comment)))
(when (and start
(< start font-lock-beg))
(setq font-lock-beg start)
(setq res t)))
(goto-char font-lock-end)
(let ((start (highlight-doxygen-inside-special-comment)))
(when start
(highlight-doxygen-move-end-of-comment)
(when (< font-lock-end (point))
(setq font-lock-end (point))
(setq res t))))
res)))
;; -------------------------------------------------------------------
;; The modes.
;;
(defvar highlight-doxygen--old-c-doc-rules nil)
(define-minor-mode highlight-doxygen-mode
"Minor mode that highlights Doxygen comments."
:group 'highlight-doxygen
;; TODO: Cache the keywords.
(if highlight-doxygen-mode
(progn
(add-to-list 'font-lock-extend-region-functions
#'highlight-doxygen-extend-region-full-comment)
(add-to-list 'font-lock-extra-managed-props 'highlight-doxygen-code)
(add-to-list 'font-lock-extra-managed-props 'highlight-doxygen-ignore)
;; Remove built-in c-doc rules, to avoid it interfering with
;; our rules.
(when (fboundp 'c-compose-keywords-list)
(let ((c-doc-keywords (c-compose-keywords-list '())))
(set (make-local-variable 'highlight-doxygen--old-c-doc-rules)
c-doc-keywords)
(font-lock-remove-keywords nil c-doc-keywords)))
(font-lock-add-keywords nil
(highlight-doxygen-compose-font-lock-keywords))
(setq font-lock-multiline t))
;; Note: `font-lock-multiline' is not restored. It may have gotten
;; set by some other minor mode. Besides, it doesn't hurt keeping
;; it set to t.
(setq font-lock-extend-region-functions
(delq 'highlight-doxygen-extend-region-full-comment
font-lock-extend-region-functions))
(setq font-lock-extra-managed-props
(delq 'highlight-doxygen-code font-lock-extra-managed-props))
(setq font-lock-extra-managed-props
(delq 'highlight-doxygen-ignore font-lock-extra-managed-props))
(font-lock-remove-keywords nil
(highlight-doxygen-compose-font-lock-keywords))
(font-lock-add-keywords nil highlight-doxygen--old-c-doc-rules))
;; As of Emacs 25, `font-lock-fontify-buffer' is not legal to
;; call, instead `font-lock-flush' should be used.
(if (fboundp 'font-lock-flush)
(font-lock-flush)
(when font-lock-mode
(with-no-warnings
(font-lock-fontify-buffer)))))
;;;###autoload
(define-global-minor-mode highlight-doxygen-global-mode highlight-doxygen-mode
(lambda ()
(when (apply #'derived-mode-p highlight-doxygen-modes)
(highlight-doxygen-mode 1)))
:group 'highlight-doxygen
:require 'highlight-doxygen)
(provide 'highlight-doxygen)
;;; highlight-doxygen.el ends here