diff options
author | Sean Whitton <spwhitton@spwhitton.name> | 2016-05-11 11:56:54 -0300 |
---|---|---|
committer | Sean Whitton <spwhitton@spwhitton.name> | 2016-05-11 11:56:54 -0300 |
commit | 883fcc703b6e912ee45480be86547a2f471a73ad (patch) | |
tree | 56a641b9db272482dda90399feb4a36c04d050ce |
Import rainbow-delimiters_2.1.3.orig.tar.xz
[dgit import orig rainbow-delimiters_2.1.3.orig.tar.xz]
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | .travis.yml | 19 | ||||
-rw-r--r-- | README.md | 75 | ||||
-rw-r--r-- | rainbow-delimiters-test.el | 286 | ||||
-rw-r--r-- | rainbow-delimiters.el | 292 |
5 files changed, 673 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c531d98 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.elc diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..6f413e7 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,19 @@ +language: emacs-lisp +env: + - EMACS=emacs23 + - EMACS=emacs24 + - EMACS=emacs-snapshot +before_install: + - sudo add-apt-repository -y ppa:cassou/emacs + - sudo add-apt-repository -y ppa:ubuntu-elisp/ppa + - sudo apt-get update -y -q + - sudo apt-get install -y $EMACS-nox +install: + - if [ "$EMACS" = "emacs23" ]; then + curl -O 'https://raw.githubusercontent.com/ohler/ert/c619b56c5bc6a866e33787489545b87d79973205/lisp/emacs-lisp/ert.el'; + fi +script: + - $EMACS -Q -batch --eval '(setq byte-compile-error-on-warn t)' -f batch-byte-compile rainbow-delimiters.el + - $EMACS -Q -batch -l rainbow-delimiters-test.el -f ert-run-tests-batch-and-exit +notifications: + email: false diff --git a/README.md b/README.md new file mode 100644 index 0000000..3f1fb58 --- /dev/null +++ b/README.md @@ -0,0 +1,75 @@ +[![MELPA](http://melpa.org/packages/rainbow-delimiters-badge.svg)](http://melpa.org/#/rainbow-delimiters) +[![MELPA Stable](http://stable.melpa.org/packages/rainbow-delimiters-badge.svg)](http://stable.melpa.org/#/rainbow-delimiters) +[![Build status](https://travis-ci.org/Fanael/rainbow-delimiters.svg?branch=master)](https://travis-ci.org/Fanael/rainbow-delimiters) + +# rainbow-delimiters + +`rainbow-delimiters` is a "rainbow parentheses"-like mode which highlights +delimiters such as parentheses, brackets or braces according to their depth. +Each successive level is highlighted in a different color. This makes it easy to +spot matching delimiters, orient yourself in the code, and tell which statements +are at a given depth. + +Great care has been taken to make this mode fast. You shouldn't see any change +in scrolling or editing speed when it's on even when working in delimiter-rich +languages like Clojure or Emacs Lisp. It can be used with any language. + +You can customize the colors `rainbow-delimiters` uses. The default colors are +intentionally subtle; they are unobtrusive enough to make the mode worth looking +at even if you usually don't like rainbow parentheses modes. A number of major +color themes such as Zenburn and Solarized have added their own faces for the +mode. + +This is the official github repository for `rainbow-delimiters`. + +The latest **release** of `rainbow-delimiters` is always found at +https://github.com/Fanael/rainbow-delimiters/tree/master + +## Installation + +The recommended way is to use [MELPA](http://melpa.org/) or +[MELPA Stable](http://stable.melpa.org/). If either is in your +`package-archives`, do + + M-x package-install RET rainbow-delimiters RET + +Otherwise, open `rainbow-delimiters.el` in Emacs and use + + M-x package-install-from-buffer + +Any other methods of installation are unsupported. + +## Usage + +To toggle the mode in the current buffer: + + M-x rainbow-delimiters-mode + +To start the mode automatically in `foo-mode`, add the following to your init +file: + + (add-hook 'foo-mode-hook #'rainbow-delimiters-mode) + +To start the mode automatically in most programming modes (Emacs 24 and above): + + (add-hook 'prog-mode-hook #'rainbow-delimiters-mode) + +### Global mode + +There's no `global-rainbow-delimiters-mode` anymore. It used to exist, but it +was impossible to keep it from breaking some major modes. It's *strongly +recommended* to use major mode hooks instead, as shown above. There's nothing +stopping you from defining `global-rainbow-delimiters-mode` yourself, but if it +breaks something, you're on your own. + +## Customization + +To customize various options, including the color theme: + + M-x customize-group rainbow-delimiters + +You can specify custom colors by customizing following faces: + * Faces take the form `rainbow-delimiters-depth-N-face`, with N being the + depth. Depth begins at 1, the outermost color. Faces exist for depths 1-9. + * The unmatched delimiter face: `rainbow-delimiters-unmatched-face`. + * The mismatched delimiter face: `rainbow-delimiters-mismatched-face`. diff --git a/rainbow-delimiters-test.el b/rainbow-delimiters-test.el new file mode 100644 index 0000000..4683a9e --- /dev/null +++ b/rainbow-delimiters-test.el @@ -0,0 +1,286 @@ +;;; rainbow-delimiters-test.el --- rainbow-delimiters test suite + +;; Author: Fanael Linithien <fanael4@gmail.com> +;; URL: https://github.com/Fanael/rainbow-delimiters + +;; This file is NOT part of GNU Emacs. + +;; Copyright (c) 2014-2015, Fanael Linithien +;; All rights reserved. +;; +;; Redistribution and use in source and binary forms, with or without +;; modification, are permitted provided that the following conditions are +;; met: +;; +;; * Redistributions of source code must retain the above copyright +;; notice, this list of conditions and the following disclaimer. +;; * Redistributions in binary form must reproduce the above copyright +;; notice, this list of conditions and the following disclaimer in the +;; documentation and/or other materials provided with the distribution. +;; +;; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +;; IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +;; TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +;; PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER +;; OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +;; EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +;; PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +;; PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +;; LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +;; NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +;; SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +;;; Commentary: + +;; `rainbow-delimiters' test suite. + +;;; Code: + +(unless noninteractive + (error "This file should only be used noninteractively")) + +(push (file-name-directory load-file-name) load-path) +(setq font-lock-verbose nil) + +(require 'rainbow-delimiters) +(require 'ert) + +(defmacro with-temp-buffer-in-mode (mode &rest body) + (declare (indent defun) (debug t)) + `(with-temp-buffer + (funcall ,mode) + (font-lock-mode) + (rainbow-delimiters-mode) + ,@body)) + +(defmacro with-string (strdecl &rest body) + (declare (indent defun) (debug t)) + `(let ((,(car strdecl) ,(cadr strdecl))) + (insert ,(car strdecl)) + (fontify-buffer) + ,@body)) + +(defun fontify-buffer () + (if (fboundp 'font-lock-ensure) + (font-lock-ensure) + (with-no-warnings (font-lock-fontify-buffer)))) + +(defun fontify-without-rainbow-delimiters (mode text) + (with-temp-buffer + (funcall mode) + (insert text) + (fontify-buffer) + (buffer-string))) + +(defun should-do-nothing (mode str) + (with-temp-buffer-in-mode mode + (with-string (str str) + (should (ert-equal-including-properties + (buffer-string) + (fontify-without-rainbow-delimiters mode str)))))) + +(ert-deftest can-enable-mode () + (with-temp-buffer + (rainbow-delimiters-mode 1) + (should rainbow-delimiters-mode))) + +(ert-deftest can-disable-mode () + (with-temp-buffer + (rainbow-delimiters-mode 1) + (rainbow-delimiters-mode 0) + (should-not rainbow-delimiters-mode))) + +(defmacro highlights-matching-delim-test (name opening closing) + `(ert-deftest ,(intern (format "highlights-matching-%s" name)) () + (with-temp-buffer-in-mode 'text-mode + (with-string (str ,(format "%cfoo%c" opening closing)) + (should (ert-equal-including-properties + (buffer-string) + (progn + (add-text-properties 0 1 '(face (rainbow-delimiters-depth-1-face)) str) + (add-text-properties 4 5 '(face (rainbow-delimiters-depth-1-face)) str) + str))))))) + +(highlights-matching-delim-test "parens" ?\( ?\)) +(highlights-matching-delim-test "brackets" ?\[ ?\]) +(highlights-matching-delim-test "braces" ?\{ ?\}) + +(defmacro highlights-matching-nested-delim-test (name opening closing) + `(ert-deftest ,(intern (format "highlights-nested-matching-%s" name)) () + (with-temp-buffer-in-mode 'text-mode + (with-string (str ,(format "%sfoo%s" (make-string 4 opening) (make-string 4 closing))) + (should (ert-equal-including-properties + (buffer-string) + (progn + (add-text-properties 0 1 '(face (rainbow-delimiters-depth-1-face)) str) + (add-text-properties 1 2 '(face (rainbow-delimiters-depth-2-face)) str) + (add-text-properties 2 3 '(face (rainbow-delimiters-depth-3-face)) str) + (add-text-properties 3 4 '(face (rainbow-delimiters-depth-4-face)) str) + (add-text-properties 7 8 '(face (rainbow-delimiters-depth-4-face)) str) + (add-text-properties 8 9 '(face (rainbow-delimiters-depth-3-face)) str) + (add-text-properties 9 10 '(face (rainbow-delimiters-depth-2-face)) str) + (add-text-properties 10 11 '(face (rainbow-delimiters-depth-1-face)) str) + str))))))) + +(highlights-matching-nested-delim-test "parens" ?\( ?\)) +(highlights-matching-nested-delim-test "brackets" ?\[ ?\]) +(highlights-matching-nested-delim-test "braces" ?\{ ?\}) + +(ert-deftest highlights-mixed-matching-delimiters () + (with-temp-buffer-in-mode 'text-mode + (with-string (str "([{(foo)}])") + (should (ert-equal-including-properties + (buffer-string) + #("([{(foo)}])" + 0 1 (face (rainbow-delimiters-depth-1-face)) + 1 2 (face (rainbow-delimiters-depth-2-face)) + 2 3 (face (rainbow-delimiters-depth-3-face)) + 3 4 (face (rainbow-delimiters-depth-4-face)) + 7 8 (face (rainbow-delimiters-depth-4-face)) + 8 9 (face (rainbow-delimiters-depth-3-face)) + 9 10 (face (rainbow-delimiters-depth-2-face)) + 10 11 (face (rainbow-delimiters-depth-1-face)))))))) + +(ert-deftest highlights-all-delimiters () + (with-temp-buffer-in-mode 'c++-mode + (with-string (str "foo<int> x;") + (should (ert-equal-including-properties + (progn + (remove-list-of-text-properties + (point-min) (point-max) '(category c-type syntax-table)) + (buffer-string)) + #("foo<int> x;" + 0 3 (face font-lock-type-face) + 3 4 (face (rainbow-delimiters-depth-1-face)) + 4 7 (face font-lock-type-face) + 7 8 (face (rainbow-delimiters-depth-1-face)) + 9 10 (face font-lock-variable-name-face))))))) + +(ert-deftest doesnt-higlight-nondelimiters-1 () + (should-do-nothing 'text-mode "foo")) + +(ert-deftest doesnt-higlight-nondelimiters-2 () + (should-do-nothing 'emacs-lisp-mode "{foo}")) + +(ert-deftest doesnt-highlight-in-comments-1 () + (should-do-nothing 'emacs-lisp-mode "; ()[]")) + +(ert-deftest doesnt-highlight-in-comments-2 () + (should-do-nothing 'pascal-mode "(* foo *)")) + +(ert-deftest doesnt-highlight-in-strings () + (should-do-nothing 'emacs-lisp-mode "\"()\"")) + +(ert-deftest highlights-unmatched () + (with-temp-buffer-in-mode 'emacs-lisp-mode + (with-string (str ")") + (should (ert-equal-including-properties + (buffer-string) + #(")" + 0 1 (face (rainbow-delimiters-unmatched-face)))))))) + +(ert-deftest highlights-mismatched () + (with-temp-buffer-in-mode 'emacs-lisp-mode + (with-string (str "(]") + (should (ert-equal-including-properties + (buffer-string) + #("(]" + 0 1 (face (rainbow-delimiters-depth-1-face)) + 1 2 (face (rainbow-delimiters-mismatched-face)))))))) + +(ert-deftest doesnt-highlight-escaped-delimiters () + (with-temp-buffer-in-mode 'emacs-lisp-mode + (with-string (str "(bar ?\\( (foo?))") + (should (ert-equal-including-properties + (buffer-string) + #("(bar ?\\( (foo?))" + 0 1 + (face (rainbow-delimiters-depth-1-face)) + 9 10 + (face (rainbow-delimiters-depth-2-face)) + 14 15 + (face (rainbow-delimiters-depth-2-face)) + 15 16 + (face (rainbow-delimiters-depth-1-face)))))))) + +(ert-deftest cycles-faces () + (let ((rainbow-delimiters-max-face-count 2)) + (with-temp-buffer-in-mode 'text-mode + (with-string (str "(((())))") + (should (ert-equal-including-properties + (buffer-string) + #("(((())))" + 0 1 (face (rainbow-delimiters-depth-1-face)) + 1 2 (face (rainbow-delimiters-depth-2-face)) + 2 3 (face (rainbow-delimiters-depth-1-face)) + 3 4 (face (rainbow-delimiters-depth-2-face)) + 4 5 (face (rainbow-delimiters-depth-2-face)) + 5 6 (face (rainbow-delimiters-depth-1-face)) + 6 7 (face (rainbow-delimiters-depth-2-face)) + 7 8 (face (rainbow-delimiters-depth-1-face))))))))) + +(ert-deftest doesnt-cycle-outermost-only-faces () + (let ((rainbow-delimiters-outermost-only-face-count 2) + (rainbow-delimiters-max-face-count 3)) + (with-temp-buffer-in-mode 'text-mode + (with-string (str "(((())))") + (should (ert-equal-including-properties + (buffer-string) + #("(((())))" + 0 1 (face (rainbow-delimiters-depth-1-face)) + 1 2 (face (rainbow-delimiters-depth-2-face)) + 2 3 (face (rainbow-delimiters-depth-3-face)) + 3 4 (face (rainbow-delimiters-depth-3-face)) + 4 5 (face (rainbow-delimiters-depth-3-face)) + 5 6 (face (rainbow-delimiters-depth-3-face)) + 6 7 (face (rainbow-delimiters-depth-2-face)) + 7 8 (face (rainbow-delimiters-depth-1-face))))))))) + +(ert-deftest highlights-already-highlighted () + (with-temp-buffer-in-mode 'diff-mode + (with-string (str "+ foo ()\n") + (should (ert-equal-including-properties + (buffer-string) + #("+ foo ()\n" + 0 1 (face diff-indicator-added) + 1 6 (face diff-added) + 6 7 (face (rainbow-delimiters-depth-1-face diff-added)) + 7 8 (face (rainbow-delimiters-depth-1-face diff-added)) + 8 9 (face diff-added))))))) + +(ert-deftest can-customize-face-picker () + (let ((rainbow-delimiters-pick-face-function + (lambda (_depth _match _loc) + 'font-lock-keyword-face))) + (with-temp-buffer-in-mode 'emacs-lisp-mode + (with-string (str "(())") + (should (ert-equal-including-properties + (buffer-string) + #("(())" + 0 1 (face (font-lock-keyword-face)) + 1 2 (face (font-lock-keyword-face)) + 2 3 (face (font-lock-keyword-face)) + 3 4 (face (font-lock-keyword-face))))))))) + +(ert-deftest face-picker-can-disable-highlighting () + (let ((rainbow-delimiters-pick-face-function + (lambda (depth match loc) + (unless (memq (char-after loc) '(?\( ?\))) + (rainbow-delimiters-default-pick-face depth match loc))))) + (should-do-nothing 'text-mode "(((())))"))) + +(ert-deftest delimiters-disabled-by-face-picker-contribute-to-depth () + (let ((rainbow-delimiters-pick-face-function + (lambda (depth match loc) + (unless (memq (char-after loc) '(?\( ?\))) + (rainbow-delimiters-default-pick-face depth match loc))))) + (with-temp-buffer-in-mode 'text-mode + (with-string (str "([])") + (should (ert-equal-including-properties + (buffer-string) + #("([])" + 1 2 (face (rainbow-delimiters-depth-2-face)) + 2 3 (face (rainbow-delimiters-depth-2-face))))))))) + +(provide 'rainbow-delimiters-test) +;;; rainbow-delimiters-test.el ends here diff --git a/rainbow-delimiters.el b/rainbow-delimiters.el new file mode 100644 index 0000000..5f30e7c --- /dev/null +++ b/rainbow-delimiters.el @@ -0,0 +1,292 @@ +;;; rainbow-delimiters.el --- Highlight brackets according to their depth -*- lexical-binding: t -*- + +;; Copyright (C) +;; 2010-2013 Jeremy Rayman +;; 2013-2016 Fanael Linithien +;; Author: Jeremy Rayman <opensource@jeremyrayman.com> +;; Fanael Linithien <fanael4@gmail.com> +;; Maintainer: Fanael Linithien <fanael4@gmail.com> +;; Created: 2010-09-02 +;; Version: 2.1.3 +;; Keywords: faces, convenience, lisp, tools +;; Homepage: https://github.com/Fanael/rainbow-delimiters + +;; Note: despite `lexical-binding', there's no Emacs 24 dependency. +;; This is merely an optimization for Emacs 24+, the code is supposed to work +;; with *both* dynamic and lexical binding. + +;; 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/>. + +;;; Installation: + +;; The recommended way is to use MELPA (http://melpa.org/) or MELPA Stable +;; (http://stable.melpa.org/). If either is in your `package-archives', do +;; M-x package-install RET rainbow-delimiters RET +;; Otherwise, open `rainbow-delimiters.el' in Emacs and use +;; M-x package-install-from-buffer +;; Any other methods of installation are unsupported. + +;;; Commentary: +;; +;; Rainbow-delimiters is a "rainbow parentheses"-like mode which highlights +;; parentheses, brackets, and braces according to their depth. Each +;; successive level is highlighted in a different color. This makes it easy +;; to spot matching delimiters, orient yourself in the code, and tell which +;; statements are at a given level. +;; +;; Great care has been taken to make this mode fast. You shouldn't see +;; any discernible change in scrolling or editing speed while using it, +;; even in delimiter-rich languages like Clojure, Lisp, and Scheme. +;; +;; Usage: +;; +;; To toggle the mode in the current buffer: +;; M-x rainbow-delimiters-mode +;; To start the mode automatically in `foo-mode', add the following to your init +;; file: +;; (add-hook 'foo-mode-hook #'rainbow-delimiters-mode) +;; To start the mode automatically in most programming modes (Emacs 24 and +;; above): +;; (add-hook 'prog-mode-hook #'rainbow-delimiters-mode) +;; +;; Customization: +;; +;; To customize various options, including the color theme: +;; M-x customize-group rainbow-delimiters +;; +;; You can specify custom colors by customizing following faces: +;; - Faces take the form `rainbow-delimiters-depth-N-face', with N being the +;; depth. Depth begins at 1, the outermost color. Faces exist for depths 1-9. +;; - The unmatched delimiter face: `rainbow-delimiters-unmatched-face'. +;; - The mismatched delimiter face: `rainbow-delimiters-mismatched-face'. + +;;; Code: + +(defgroup rainbow-delimiters nil + "Highlight nested parentheses, brackets, and braces according to their depth." + :prefix "rainbow-delimiters-" + :link '(url-link :tag "Website for rainbow-delimiters" + "https://github.com/Fanael/rainbow-delimiters") + :group 'applications) + +(defgroup rainbow-delimiters-faces nil + "Faces for successively nested pairs of delimiters. + +When depth exceeds innermost defined face, colors cycle back through." + :group 'rainbow-delimiters + :group 'faces + :link '(custom-group-link "rainbow-delimiters") + :prefix "rainbow-delimiters-") + +(defcustom rainbow-delimiters-pick-face-function + #'rainbow-delimiters-default-pick-face + "The function used to pick a face used to highlight a delimiter. +The function should take three arguments (DEPTH MATCH LOC), where: + - DEPTH is the delimiter depth; when zero or negative, it's an unmatched + delimiter. + - MATCH is nil iff the delimiter is a mismatched closing delimiter. + - LOC is the location of the delimiter. +The function should return a value suitable to use as a value of the `face' text +property, or nil, in which case the delimiter is not highlighted. +The function should not move the point or mark or change the match data." + :tag "Pick face function" + :type 'function + :group 'rainbow-delimiters) + +(defface rainbow-delimiters-unmatched-face + '((((background light)) (:foreground "#88090B")) + (((background dark)) (:foreground "#88090B"))) + "Face to highlight unmatched closing delimiters in." + :group 'rainbow-delimiters-faces) + +(defface rainbow-delimiters-mismatched-face + '((t :inherit rainbow-delimiters-unmatched-face)) + "Face to highlight mismatched closing delimiters in." + :group 'rainbow-delimiters-faces) + +(eval-when-compile + (defmacro rainbow-delimiters--define-depth-faces () + (let ((faces '()) + (light-colors ["#707183" "#7388d6" "#909183" "#709870" "#907373" + "#6276ba" "#858580" "#80a880" "#887070"]) + (dark-colors ["grey55" "#93a8c6" "#b0b1a3" "#97b098" "#aebed8" + "#b0b0b3" "#90a890" "#a2b6da" "#9cb6ad"])) + (dotimes (i 9) + (push `(defface ,(intern (format "rainbow-delimiters-depth-%d-face" (1+ i))) + '((((class color) (background light)) :foreground ,(aref light-colors i)) + (((class color) (background dark)) :foreground ,(aref dark-colors i))) + ,(format "Nested delimiter face, depth %d." (1+ i)) + :group 'rainbow-delimiters-faces) + faces)) + `(progn ,@faces)))) +(rainbow-delimiters--define-depth-faces) + +(defcustom rainbow-delimiters-max-face-count 9 + "Number of faces defined for highlighting delimiter levels. + +Determines depth at which to cycle through faces again. + +It's safe to change this variable provided that for all integers from 1 to the +new value inclusive, a face `rainbow-delimiters-depth-N-face' is defined." + :type 'integer + :group 'rainbow-delimiters) + +(defcustom rainbow-delimiters-outermost-only-face-count 0 + "Number of faces to be used only for N outermost delimiter levels. + +This should be smaller than `rainbow-delimiters-max-face-count'." + :type 'integer + :group 'rainbow-delimiters) + + +(defun rainbow-delimiters-default-pick-face (depth match _loc) + "Return a face name appropriate for nesting depth DEPTH. +DEPTH and MATCH are as in `rainbow-delimiters-pick-face-function'. + +The returned value is either `rainbow-delimiters-unmatched-face', +`rainbow-delimiters-mismatched-face', or one of the +`rainbow-delimiters-depth-N-face' faces, obeying +`rainbow-delimiters-max-face-count' and +`rainbow-delimiters-outermost-only-face-count'." + (cond + ((<= depth 0) + 'rainbow-delimiters-unmatched-face) + ((not match) + 'rainbow-delimiters-mismatched-face) + (t + (intern-soft + (concat "rainbow-delimiters-depth-" + (number-to-string + (if (<= depth rainbow-delimiters-max-face-count) + ;; Our nesting depth has a face defined for it. + depth + ;; Deeper than # of defined faces; cycle back through to + ;; `rainbow-delimiters-outermost-only-face-count' + 1. + ;; Return face # that corresponds to current nesting level. + (+ 1 rainbow-delimiters-outermost-only-face-count + (mod (- depth rainbow-delimiters-max-face-count 1) + (- rainbow-delimiters-max-face-count + rainbow-delimiters-outermost-only-face-count))))) + "-face"))))) + +(defun rainbow-delimiters--apply-color (loc depth match) + "Highlight a single delimiter at LOC according to DEPTH. + +LOC is the location of the character to add text properties to. +DEPTH is the nested depth at LOC, which determines the face to use. +MATCH is nil iff it's a mismatched closing delimiter." + (let ((face (funcall rainbow-delimiters-pick-face-function depth match loc))) + (when face + (font-lock-prepend-text-property loc (1+ loc) 'face face)))) + +(defun rainbow-delimiters--char-ineligible-p (loc ppss delim-syntax-code) + "Return t if char at LOC should not be highlighted. +PPSS is the `parse-partial-sexp' state at LOC. +DELIM-SYNTAX-CODE is the `car' of a raw syntax descriptor at LOC. + +Returns t if char at loc meets one of the following conditions: +- Inside a string. +- Inside a comment. +- Is an escaped char, e.g. ?\)" + (or + (nth 3 ppss) ; inside string? + (nth 4 ppss) ; inside comment? + (nth 5 ppss) ; escaped according to the syntax table? + ;; Note: no need to consider single-char openers, they're already handled + ;; by looking at ppss. + (cond + ;; Two character opener, LOC at the first character? + ((/= 0 (logand #x10000 delim-syntax-code)) + (/= 0 (logand #x20000 (or (car (syntax-after (1+ loc))) 0)))) + ;; Two character opener, LOC at the second character? + ((/= 0 (logand #x20000 delim-syntax-code)) + (/= 0 (logand #x10000 (or (car (syntax-after (1- loc))) 0)))) + (t + nil)))) + +;; Main function called by font-lock. +(defun rainbow-delimiters--propertize (end) + "Highlight delimiters in region between point and END. + +Used by font-lock for dynamic highlighting." + (let* ((last-ppss-pos (point)) + (ppss (syntax-ppss))) + (while (> end (progn (skip-syntax-forward "^()" end) + (point))) + (let* ((delim-pos (point)) + (delim-syntax (syntax-after delim-pos))) + (setq ppss (parse-partial-sexp last-ppss-pos delim-pos nil nil ppss)) + (setq last-ppss-pos delim-pos) + ;; `skip-syntax-forward' leaves the point at the delimiter, move past + ;; it. + (forward-char) + (let ((delim-syntax-code (car delim-syntax))) + (cond + ((rainbow-delimiters--char-ineligible-p delim-pos ppss delim-syntax-code) + nil) + ((= 4 (logand #xFFFF delim-syntax-code)) + ;; The (1+ ...) is needed because `parse-partial-sexp' returns the + ;; depth at the opening delimiter, not in the block being started. + (rainbow-delimiters--apply-color delim-pos (1+ (nth 0 ppss)) t)) + (t + ;; Not an opening delimiter, so it's a closing delimiter. + (let ((matches-p (eq (cdr delim-syntax) (char-after (nth 1 ppss))))) + (rainbow-delimiters--apply-color delim-pos (nth 0 ppss) matches-p)))))))) + ;; We already fontified the delimiters, tell font-lock there's nothing more + ;; to do. + nil) + +;; NB: no face defined here because we apply the faces ourselves instead of +;; leaving that to font-lock. +(defconst rainbow-delimiters--font-lock-keywords + '(rainbow-delimiters--propertize)) + +;;;###autoload +(define-minor-mode rainbow-delimiters-mode + "Highlight nested parentheses, brackets, and braces according to their depth." + nil "" nil ; No modeline lighter - it's already obvious when the mode is on. + (font-lock-remove-keywords nil rainbow-delimiters--font-lock-keywords) + (when rainbow-delimiters-mode + (font-lock-add-keywords nil rainbow-delimiters--font-lock-keywords 'append) + (set (make-local-variable 'jit-lock-contextually) t) + (when (or (bound-and-true-p syntax-begin-function) + (bound-and-true-p font-lock-beginning-of-syntax-function)) + ;; We're going to modify `syntax-begin-function', so flush the cache to + ;; avoid getting cached values that used the old value. + (syntax-ppss-flush-cache 0)) + ;; `syntax-begin-function' may break the assumption we rely on that + ;; `syntax-ppss' is exactly equivalent to `parse-partial-sexp' from + ;; `point-min'. Just don't use it, the performance hit should be negligible. + (when (boundp 'syntax-begin-function) + (set (make-local-variable 'syntax-begin-function) nil)) + ;; Obsolete equivalent of `syntax-begin-function'. + (when (boundp 'font-lock-beginning-of-syntax-function) + (set (make-local-variable 'font-lock-beginning-of-syntax-function) nil))) + (when font-lock-mode + (if (fboundp 'font-lock-flush) + (font-lock-flush) + (with-no-warnings (font-lock-fontify-buffer))))) + +;;;###autoload +(defun rainbow-delimiters-mode-enable () + "Enable `rainbow-delimiters-mode'." + (rainbow-delimiters-mode 1)) + +;;;###autoload +(defun rainbow-delimiters-mode-disable () + "Disable `rainbow-delimiters-mode'." + (rainbow-delimiters-mode 0)) + +(provide 'rainbow-delimiters) +;;; rainbow-delimiters.el ends here |