From mboxrd@z Thu Jan 1 00:00:00 1970 Path: news.gmane.org!.POSTED!not-for-mail From: Tom Tromey Newsgroups: gmane.emacs.bugs Subject: bug#25525: 25.1.90; add color highlighting to css mode Date: Wed, 25 Jan 2017 01:06:48 -0700 Message-ID: <87mvefh8br.fsf@tromey.com> NNTP-Posting-Host: blaine.gmane.org Mime-Version: 1.0 Content-Type: multipart/mixed; boundary="=-=-=" X-Trace: blaine.gmane.org 1485331699 8814 195.159.176.226 (25 Jan 2017 08:08:19 GMT) X-Complaints-To: usenet@blaine.gmane.org NNTP-Posting-Date: Wed, 25 Jan 2017 08:08:19 +0000 (UTC) To: 25525@debbugs.gnu.org Original-X-From: bug-gnu-emacs-bounces+geb-bug-gnu-emacs=m.gmane.org@gnu.org Wed Jan 25 09:08:10 2017 Return-path: Envelope-to: geb-bug-gnu-emacs@m.gmane.org Original-Received: from lists.gnu.org ([208.118.235.17]) by blaine.gmane.org with esmtp (Exim 4.84_2) (envelope-from ) id 1cWIcj-0001Tr-L2 for geb-bug-gnu-emacs@m.gmane.org; Wed, 25 Jan 2017 09:08:10 +0100 Original-Received: from localhost ([::1]:57958 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1cWIco-000484-TU for geb-bug-gnu-emacs@m.gmane.org; Wed, 25 Jan 2017 03:08:14 -0500 Original-Received: from eggs.gnu.org ([2001:4830:134:3::10]:50411) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1cWIcf-00047s-HL for bug-gnu-emacs@gnu.org; Wed, 25 Jan 2017 03:08:08 -0500 Original-Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1cWIcc-00040z-7v for bug-gnu-emacs@gnu.org; Wed, 25 Jan 2017 03:08:05 -0500 Original-Received: from debbugs.gnu.org ([208.118.235.43]:43249) by eggs.gnu.org with esmtps (TLS1.0:RSA_AES_128_CBC_SHA1:16) (Exim 4.71) (envelope-from ) id 1cWIcc-00040v-5M for bug-gnu-emacs@gnu.org; Wed, 25 Jan 2017 03:08:02 -0500 Original-Received: from Debian-debbugs by debbugs.gnu.org with local (Exim 4.84_2) (envelope-from ) id 1cWIcb-0006aC-OU for bug-gnu-emacs@gnu.org; Wed, 25 Jan 2017 03:08:01 -0500 X-Loop: help-debbugs@gnu.org Resent-From: Tom Tromey Original-Sender: "Debbugs-submit" Resent-CC: bug-gnu-emacs@gnu.org Resent-Date: Wed, 25 Jan 2017 08:08:01 +0000 Resent-Message-ID: Resent-Sender: help-debbugs@gnu.org X-GNU-PR-Message: report 25525 X-GNU-PR-Package: emacs X-GNU-PR-Keywords: X-Debbugs-Original-To: bug-gnu-emacs@gnu.org Original-Received: via spool by submit@debbugs.gnu.org id=B.148533166425277 (code B ref -1); Wed, 25 Jan 2017 08:08:01 +0000 Original-Received: (at submit) by debbugs.gnu.org; 25 Jan 2017 08:07:44 +0000 Original-Received: from localhost ([127.0.0.1]:41448 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1cWIcJ-0006Zc-O8 for submit@debbugs.gnu.org; Wed, 25 Jan 2017 03:07:44 -0500 Original-Received: from eggs.gnu.org ([208.118.235.92]:59362) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1cWIcH-0006ZP-LG for submit@debbugs.gnu.org; Wed, 25 Jan 2017 03:07:42 -0500 Original-Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1cWIc4-0003oR-QF for submit@debbugs.gnu.org; Wed, 25 Jan 2017 03:07:36 -0500 Original-Received: from lists.gnu.org ([2001:4830:134:3::11]:39543) by eggs.gnu.org with esmtps (TLS1.0:RSA_AES_256_CBC_SHA1:32) (Exim 4.71) (envelope-from ) id 1cWIc4-0003oM-MT for submit@debbugs.gnu.org; Wed, 25 Jan 2017 03:07:28 -0500 Original-Received: from eggs.gnu.org ([2001:4830:134:3::10]:50269) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1cWIc1-0003sM-HJ for bug-gnu-emacs@gnu.org; Wed, 25 Jan 2017 03:07:28 -0500 Original-Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1cWIby-0003kj-6D for bug-gnu-emacs@gnu.org; Wed, 25 Jan 2017 03:07:25 -0500 Original-Received: from gproxy9-pub.mail.unifiedlayer.com ([69.89.20.122]:47995 helo=gproxy9.mail.unifiedlayer.com) by eggs.gnu.org with esmtps (TLS1.0:DHE_RSA_AES_256_CBC_SHA1:32) (Exim 4.71) (envelope-from ) id 1cWIbx-0003gQ-ID for bug-gnu-emacs@gnu.org; Wed, 25 Jan 2017 03:07:22 -0500 Original-Received: from CMOut01 (unknown [10.0.90.82]) by gproxy9.mail.unifiedlayer.com (Postfix) with ESMTP id 010FA1E0C75 for ; Wed, 25 Jan 2017 01:06:57 -0700 (MST) Original-Received: from box522.bluehost.com ([74.220.219.122]) by CMOut01 with id cY6q1u00N2f2jeq01Y6ttl; Wed, 25 Jan 2017 01:06:55 -0700 X-Authority-Analysis: v=2.1 cv=H75InYoi c=1 sm=1 tr=0 a=GsOEXm/OWkKvwdLVJsfwcA==:117 a=GsOEXm/OWkKvwdLVJsfwcA==:17 a=L9H7d07YOLsA:10 a=9cW_t1CCXrUA:10 a=s5jvgZ67dGcA:10 a=IgFoBzBjUZAA:10 a=6DmWVQPIf7JAJQnUgk0A:9 a=zstS-IiYAAAA:8 a=BcXV-_4havjdJU-tIZUA:9 a=EJsdeB0a1gU3Vzka:21 a=kL2s3ZrwCcusjf-_:21 a=VDEtfpoToLGKPWCa6wMA:9 a=TdWOT4xzaEZjwDgE:21 a=N6cwv5yv1pgz7ggB:21 a=4G6NA9xxw8l3yy4pmD5M:22 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=tromey.com; s=default; h=Content-Type:MIME-Version:Message-ID:Date:Subject:To:From: Sender:Reply-To:Cc:Content-Transfer-Encoding:Content-ID:Content-Description: Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID: In-Reply-To:References:List-Id:List-Help:List-Unsubscribe:List-Subscribe: List-Post:List-Owner:List-Archive; bh=RpS341rkt3Pc/jrGLwm78kmSvkR0UC1DQa+AwAzxA+Y=; b=SKW5X49eg4HZBmgVyv5gwQghEJ Wf/ogG1xpDn+kZDBiltRgFg2UnjohC6lEYFXrDkNVXtiErT/L4QMFLx0oOlhQZApbBz/VhVkvSWlF m0ItA8fcQUGOxOVPOBrO3nLO1; Original-Received: from 174-16-146-181.hlrn.qwest.net ([174.16.146.181]:49120 helo=bapiya) by box522.bluehost.com with esmtpsa (TLSv1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.87) (envelope-from ) id 1cWIbT-00065y-9Z; Wed, 25 Jan 2017 01:06:51 -0700 X-Attribution: Tom X-AntiAbuse: This header was added to track abuse, please include it with any abuse report X-AntiAbuse: Primary Hostname - box522.bluehost.com X-AntiAbuse: Original Domain - gnu.org X-AntiAbuse: Originator/Caller UID/GID - [47 12] / [47 12] X-AntiAbuse: Sender Address Domain - tromey.com X-BWhitelist: no X-Source-IP: 174.16.146.181 X-Exim-ID: 1cWIbT-00065y-9Z X-Source: X-Source-Args: X-Source-Dir: X-Source-Sender: 174-16-146-181.hlrn.qwest.net (bapiya) [174.16.146.181]:49120 X-Source-Auth: tom+tromey.com X-Email-Count: 1 X-Source-Cap: ZWx5bnJvYmk7ZWx5bnJvYmk7Ym94NTIyLmJsdWVob3N0LmNvbQ== X-detected-operating-system: by eggs.gnu.org: GNU/Linux 3.x [fuzzy] X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.6.x X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic] X-Received-From: 208.118.235.43 X-BeenThere: bug-gnu-emacs@gnu.org List-Id: "Bug reports for GNU Emacs, the Swiss army knife of text editors" List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: bug-gnu-emacs-bounces+geb-bug-gnu-emacs=m.gmane.org@gnu.org Original-Sender: "bug-gnu-emacs" Xref: news.gmane.org gmane.emacs.bugs:128441 Archived-At: --=-=-= Content-Type: text/plain This patch adds color highlighting to CSS mode. In particular, this provides a new jit-lock function that recognizes the various forms of CSS color syntax, including some css-color-4 additions. When such a color is seen, the background color of the text is set to the color itself. In order to remain readable, the foreground is set to a contrasting color, and a box is put around the text (this helps distinguish colors that are close to the buffer's background color). While doing this I noticed that css-mode was missing a few named colors specified by CSS, in particular: ("darkgrey" "darkslategrey" "dimgrey" "grey" "lightgrey" "lightslategrey" "slategrey") This patch fixes this problem as well. --=-=-= Content-Type: text/x-patch Content-Disposition: inline; filename=P Content-Description: the patch commit 2b873579a2b821c073826eed5bc199216fbc14ae Author: Tom Tromey Date: Wed Jan 25 00:53:49 2017 -0700 add color highlighting to css-mode * lisp/textmodes/css-mode.el (css--color-map): New constant. (css-value-class-alist): Use css--color-map. (css--number-regexp, css--percent-regexp) (css--number-or-percent-regexp, css--angle-regexp): New constants. (css--color-skip-blanks, css--rgb-color, css--hsl-color): New functions. (css--colors-regexp): New constant. (css--hex-color, css--compute-color, css--contrasty-color) (css--fontify-colors): New functions. (css-mode): Register css--fontify-colors with jit-lock. * test/lisp/textmodes/css-mode-tests.el (css-test-property-values): Update. (css-test-rgb-parser, css-test-hsl-parser): New tests. diff --git a/lisp/textmodes/css-mode.el b/lisp/textmodes/css-mode.el index c81c3f6..c4d86f5 100644 --- a/lisp/textmodes/css-mode.el +++ b/lisp/textmodes/css-mode.el @@ -32,6 +32,9 @@ ;;; Code: +(require 'cl-lib) +(require 'cl-macs) +(require 'color) (require 'seq) (require 'sgml-mode) (require 'smie) @@ -453,8 +456,157 @@ css-property-ids (mapcar #'car css-property-alist) "Identifiers for properties.") +(defconst css--color-map + '(("black" . "#000000") + ("silver" . "#c0c0c0") + ("gray" . "#808080") + ("white" . "#ffffff") + ("maroon" . "#800000") + ("red" . "#ff0000") + ("purple" . "#800080") + ("fuchsia" . "#ff00ff") + ("green" . "#008000") + ("lime" . "#00ff00") + ("olive" . "#808000") + ("yellow" . "#ffff00") + ("navy" . "#000080") + ("blue" . "#0000ff") + ("teal" . "#008080") + ("aqua" . "#00ffff") + ("orange" . "#ffa500") + ("aliceblue" . "#f0f8ff") + ("antiquewhite" . "#faebd7") + ("aquamarine" . "#7fffd4") + ("azure" . "#f0ffff") + ("beige" . "#f5f5dc") + ("bisque" . "#ffe4c4") + ("blanchedalmond" . "#ffebcd") + ("blueviolet" . "#8a2be2") + ("brown" . "#a52a2a") + ("burlywood" . "#deb887") + ("cadetblue" . "#5f9ea0") + ("chartreuse" . "#7fff00") + ("chocolate" . "#d2691e") + ("coral" . "#ff7f50") + ("cornflowerblue" . "#6495ed") + ("cornsilk" . "#fff8dc") + ("crimson" . "#dc143c") + ("darkblue" . "#00008b") + ("darkcyan" . "#008b8b") + ("darkgoldenrod" . "#b8860b") + ("darkgray" . "#a9a9a9") + ("darkgreen" . "#006400") + ("darkgrey" . "#a9a9a9") + ("darkkhaki" . "#bdb76b") + ("darkmagenta" . "#8b008b") + ("darkolivegreen" . "#556b2f") + ("darkorange" . "#ff8c00") + ("darkorchid" . "#9932cc") + ("darkred" . "#8b0000") + ("darksalmon" . "#e9967a") + ("darkseagreen" . "#8fbc8f") + ("darkslateblue" . "#483d8b") + ("darkslategray" . "#2f4f4f") + ("darkslategrey" . "#2f4f4f") + ("darkturquoise" . "#00ced1") + ("darkviolet" . "#9400d3") + ("deeppink" . "#ff1493") + ("deepskyblue" . "#00bfff") + ("dimgray" . "#696969") + ("dimgrey" . "#696969") + ("dodgerblue" . "#1e90ff") + ("firebrick" . "#b22222") + ("floralwhite" . "#fffaf0") + ("forestgreen" . "#228b22") + ("gainsboro" . "#dcdcdc") + ("ghostwhite" . "#f8f8ff") + ("gold" . "#ffd700") + ("goldenrod" . "#daa520") + ("greenyellow" . "#adff2f") + ("grey" . "#808080") + ("honeydew" . "#f0fff0") + ("hotpink" . "#ff69b4") + ("indianred" . "#cd5c5c") + ("indigo" . "#4b0082") + ("ivory" . "#fffff0") + ("khaki" . "#f0e68c") + ("lavender" . "#e6e6fa") + ("lavenderblush" . "#fff0f5") + ("lawngreen" . "#7cfc00") + ("lemonchiffon" . "#fffacd") + ("lightblue" . "#add8e6") + ("lightcoral" . "#f08080") + ("lightcyan" . "#e0ffff") + ("lightgoldenrodyellow" . "#fafad2") + ("lightgray" . "#d3d3d3") + ("lightgreen" . "#90ee90") + ("lightgrey" . "#d3d3d3") + ("lightpink" . "#ffb6c1") + ("lightsalmon" . "#ffa07a") + ("lightseagreen" . "#20b2aa") + ("lightskyblue" . "#87cefa") + ("lightslategray" . "#778899") + ("lightslategrey" . "#778899") + ("lightsteelblue" . "#b0c4de") + ("lightyellow" . "#ffffe0") + ("limegreen" . "#32cd32") + ("linen" . "#faf0e6") + ("mediumaquamarine" . "#66cdaa") + ("mediumblue" . "#0000cd") + ("mediumorchid" . "#ba55d3") + ("mediumpurple" . "#9370db") + ("mediumseagreen" . "#3cb371") + ("mediumslateblue" . "#7b68ee") + ("mediumspringgreen" . "#00fa9a") + ("mediumturquoise" . "#48d1cc") + ("mediumvioletred" . "#c71585") + ("midnightblue" . "#191970") + ("mintcream" . "#f5fffa") + ("mistyrose" . "#ffe4e1") + ("moccasin" . "#ffe4b5") + ("navajowhite" . "#ffdead") + ("oldlace" . "#fdf5e6") + ("olivedrab" . "#6b8e23") + ("orangered" . "#ff4500") + ("orchid" . "#da70d6") + ("palegoldenrod" . "#eee8aa") + ("palegreen" . "#98fb98") + ("paleturquoise" . "#afeeee") + ("palevioletred" . "#db7093") + ("papayawhip" . "#ffefd5") + ("peachpuff" . "#ffdab9") + ("peru" . "#cd853f") + ("pink" . "#ffc0cb") + ("plum" . "#dda0dd") + ("powderblue" . "#b0e0e6") + ("rosybrown" . "#bc8f8f") + ("royalblue" . "#4169e1") + ("saddlebrown" . "#8b4513") + ("salmon" . "#fa8072") + ("sandybrown" . "#f4a460") + ("seagreen" . "#2e8b57") + ("seashell" . "#fff5ee") + ("sienna" . "#a0522d") + ("skyblue" . "#87ceeb") + ("slateblue" . "#6a5acd") + ("slategray" . "#708090") + ("slategrey" . "#708090") + ("snow" . "#fffafa") + ("springgreen" . "#00ff7f") + ("steelblue" . "#4682b4") + ("tan" . "#d2b48c") + ("thistle" . "#d8bfd8") + ("tomato" . "#ff6347") + ("turquoise" . "#40e0d0") + ("violet" . "#ee82ee") + ("wheat" . "#f5deb3") + ("whitesmoke" . "#f5f5f5") + ("yellowgreen" . "#9acd32") + ("rebeccapurple" . "#663399")) + "Map CSS named color to their hex RGB value.") + (defconst css-value-class-alist - '((absolute-size + `((absolute-size "xx-small" "x-small" "small" "medium" "large" "x-large" "xx-large") (alphavalue number) @@ -506,36 +658,7 @@ css-value-class-alist (line-width length "thin" "medium" "thick") (linear-gradient "linear-gradient()") (margin-width "auto" length percentage) - (named-color - "aliceblue" "antiquewhite" "aqua" "aquamarine" "azure" "beige" - "bisque" "black" "blanchedalmond" "blue" "blueviolet" "brown" - "burlywood" "cadetblue" "chartreuse" "chocolate" "coral" - "cornflowerblue" "cornsilk" "crimson" "cyan" "darkblue" - "darkcyan" "darkgoldenrod" "darkgray" "darkgreen" "darkkhaki" - "darkmagenta" "darkolivegreen" "darkorange" "darkorchid" - "darkred" "darksalmon" "darkseagreen" "darkslateblue" - "darkslategray" "darkturquoise" "darkviolet" "deeppink" - "deepskyblue" "dimgray" "dodgerblue" "firebrick" "floralwhite" - "forestgreen" "fuchsia" "gainsboro" "ghostwhite" "gold" - "goldenrod" "gray" "green" "greenyellow" "honeydew" "hotpink" - "indianred" "indigo" "ivory" "khaki" "lavender" "lavenderblush" - "lawngreen" "lemonchiffon" "lightblue" "lightcoral" "lightcyan" - "lightgoldenrodyellow" "lightgray" "lightgreen" "lightpink" - "lightsalmon" "lightseagreen" "lightskyblue" "lightslategray" - "lightsteelblue" "lightyellow" "lime" "limegreen" "linen" - "magenta" "maroon" "mediumaquamarine" "mediumblue" "mediumorchid" - "mediumpurple" "mediumseagreen" "mediumslateblue" - "mediumspringgreen" "mediumturquoise" "mediumvioletred" - "midnightblue" "mintcream" "mistyrose" "moccasin" "navajowhite" - "navy" "oldlace" "olive" "olivedrab" "orange" "orangered" - "orchid" "palegoldenrod" "palegreen" "paleturquoise" - "palevioletred" "papayawhip" "peachpuff" "peru" "pink" "plum" - "powderblue" "purple" "rebeccapurple" "red" "rosybrown" - "royalblue" "saddlebrown" "salmon" "sandybrown" "seagreen" - "seashell" "sienna" "silver" "skyblue" "slateblue" "slategray" - "snow" "springgreen" "steelblue" "tan" "teal" "thistle" "tomato" - "turquoise" "violet" "wheat" "white" "whitesmoke" "yellow" - "yellowgreen") + (named-color . ,(mapcar #'car css--color-map)) (number "calc()") (numeric-figure-values "lining-nums" "oldstyle-nums") (numeric-fraction-values "diagonal-fractions" "stacked-fractions") @@ -726,6 +849,196 @@ css-font-lock-keywords (defvar css-font-lock-defaults '(css-font-lock-keywords nil t)) +(defconst css--number-regexp + "\\(\\(?:[0-9]*\\.[0-9]+\\(?:[eE][0-9]+\\)?\\)\\|[0-9]+\\)" + "A regular expression matching a CSS number.") + +(defconst css--percent-regexp "\\([0-9]+\\)%" + "A regular expression matching a CSS percentage.") + +(defconst css--number-or-percent-regexp + (concat "\\(?:" css--percent-regexp "\\)\\|\\(?:" css--number-regexp "\\)") + "A regular expression matching a CSS number or a CSS percentage.") + +(defconst css--angle-regexp + (concat css--number-regexp + (regexp-opt '("deg" "grad" "rad" "turn") t) + "?") + "A regular expression matching a CSS angle.") + +(defun css--color-skip-blanks () + "Skip blanks and comments." + (while (forward-comment 1))) + +(cl-defun css--rgb-color () + "Parse a CSS rgb() or rgba() color. +Point should be just after the open paren. +Returns a hex RGB color, or nil if the color could not be recognized. +This recognizes CSS-color-4 extensions." + (let ((result '()) + (iter 0)) + (while (< iter 4) + (css--color-skip-blanks) + (unless (looking-at css--number-or-percent-regexp) + (cl-return-from css--css-4-rgb nil)) + (let* ((is-percent (match-beginning 1)) + (str (match-string (if is-percent 1 2))) + (number (string-to-number str))) + (when is-percent + (setq number (* 255 (/ number 100.0)))) + ;; Don't push the alpha. + (when (< iter 3) + (push (min (max 0 (truncate number)) 255) result)) + (goto-char (match-end 0)) + (css--color-skip-blanks) + (cl-incf iter) + ;; Accept a superset of the CSS syntax since I'm feeling lazy. + (when (and (= (skip-chars-forward ",/") 0) + (= iter 3)) + ;; The alpha is optional. + (cl-incf iter)) + (css--color-skip-blanks))) + (when (looking-at ")") + (forward-char) + (apply #'format "#%02x%02x%02x" (nreverse result))))) + +(cl-defun css--hsl-color () + "Parse a CSS hsl() or hsla() color. +Point should be just after the open paren. +Returns a hex RGB color, or nil if the color could not be recognized. +This recognizes CSS-color-4 extensions." + (let ((result '())) + ;; First parse the hue. + (css--color-skip-blanks) + (unless (looking-at css--angle-regexp) + (cl-return-from css--hsl-color nil)) + (let ((hue (string-to-number (match-string 1))) + (unit (match-string 2))) + (goto-char (match-end 0)) + ;; Note that here "turn" is just passed through. + (cond + ((or (not unit) (equal unit "deg")) + ;; Degrees. + (setq hue (/ hue 360.0))) + ((equal unit "grad") + (setq hue (/ hue 400.0))) + ((equal unit "rad") + (setq hue (/ hue (* 2 float-pi))))) + (push (mod hue 1.0) result)) + (dotimes (_ 2) + (skip-chars-forward ",") + (css--color-skip-blanks) + (unless (looking-at css--percent-regexp) + (cl-return-from css--hsl-color nil)) + (let ((number (string-to-number (match-string 1)))) + (setq number (/ number 100.0)) + (push (min (max number 0.0) 1.0) result) + (goto-char (match-end 0)) + (css--color-skip-blanks))) + (css--color-skip-blanks) + ;; Accept a superset of the CSS syntax since I'm feeling lazy. + (when (> (skip-chars-forward ",/") 0) + (css--color-skip-blanks) + (unless (looking-at css--number-or-percent-regexp) + (cl-return-from css--hsl-color nil)) + (goto-char (match-end 0)) + (css--color-skip-blanks)) + (when (looking-at ")") + (forward-char) + (apply #'color-rgb-to-hex + (apply #'color-hsl-to-rgb (nreverse result)))))) + +(defconst css--colors-regexp + (concat + ;; Named colors. + (regexp-opt (mapcar #'car css--color-map) t) + "\\|" + ;; Short hex. css-color-4 adds alpha. + "\\(#[0-9a-fA-F]\\{3,4\\}\\b\\)" + "\\|" + ;; Long hex. css-color-4 adds alpha. + "\\(#\\(?:[0-9a-fA-F][0-9a-fA-F]\\)\\{3,4\\}\\b\\)" + "\\|" + ;; RGB. + "\\(rgba?(\\)" + "\\|" + ;; HSL. + "\\(hsla?(\\)") + "A regular expression that matches the start of a CSS color.") + +(defun css--hex-color (str) + "Convert a CSS hex color to an Emacs hex color. +STR is the incoming CSS hex color. +This function simply drops any transparency." + ;; Either #RGB or #RRGGBB, drop the "A" or "AA". + (if (> (length str) 4) + (substring str 0 7) + (substring str 0 4))) + +(defun css--compute-color () + "Return the CSS color at point. +Point should be just after the start of a CSS color, as recognized +by `css--colors-regexp'. This function will either return the color, +as a hex RGB string; or `nil' if no color could be recognized. When +this function returns, point will be at the end of the recognized +color." + (let ((match (downcase (match-string 0)))) + (cond + ((eq (aref match 0) ?#) + (css--hex-color match)) + ((member match '("rgb(" "rgba(")) + (css--rgb-color)) + ((member match '("hsl(" "hsla(")) + (css--hsl-color)) + ;; Evaluate to the color if the name is found. + ((cdr (assoc match css--color-map))) + (t + (error "invalid case in css--compute-color"))))) + +(defun css--contrasty-color (name) + "Return a color that contrasts with NAME. +NAME is of any form accepted by `color-name-to-rgb'. +The returned color will be usable by Emacs and will contrast +with NAME; in particular so that if NAME is used as a background +color, the returned color can be used as the foreground and still +be readable." + (let* ((color (color-name-to-rgb name)) + (red (car color)) + (green (cadr color)) + (blue (cl-caddr color)) + (luma (+ (* 0.299 red) (* 0.587 green) (* 0.114 blue)))) + (if (> luma 0.5) + "black" + "white"))) + +(defun css--fontify-colors (start end) + "Fontify CSS colors between START and END. +START and END are buffer positions. +This function is used via `jit-lock-register'." + (message "FONTIFY %S %S" start end) + (save-excursion + (let ((case-fold-search t)) + (goto-char start) + (while (re-search-forward css--colors-regexp end t) + ;; Skip comments and strings. + (unless (nth 8 (syntax-ppss)) + (let ((start (match-beginning 0)) + (color (css--compute-color))) + (when color + (with-silent-modifications + ;; Use the color as the background, to make it more + ;; clear. Use a contrasting color as the foreground, + ;; to make it readable. Finally, have a small box + ;; using the existing foreground color, to make sure + ;; it stands out a bit from any other text; in + ;; particular this is nice when the color matches the + ;; buffer's background color. + (add-text-properties + start (point) + (list 'face (list :background color + :foreground (css--contrasty-color color) + :box '(:line-width -1)))))))))))) + (defcustom css-indent-offset 4 "Basic size of one indentation step." :version "22.2" @@ -945,6 +1258,7 @@ css-mode :backward-token #'css-smie--backward-token) (setq-local electric-indent-chars (append css-electric-keys electric-indent-chars)) + (jit-lock-register #'css--fontify-colors) (add-hook 'completion-at-point-functions #'css-completion-at-point nil 'local)) diff --git a/test/lisp/textmodes/css-mode-tests.el b/test/lisp/textmodes/css-mode-tests.el index 6eb32ea..f058bcf 100644 --- a/test/lisp/textmodes/css-mode-tests.el +++ b/test/lisp/textmodes/css-mode-tests.el @@ -58,7 +58,7 @@ ;; Check that the `color' property doesn't cause infinite recursion ;; because it refers to the value class of the same name. - (should (= (length (css--property-values "color")) 147))) + (should (= (length (css--property-values "color")) 152))) (ert-deftest css-test-property-value-cache () "Test that `css--property-value-cache' is in use." @@ -218,5 +218,40 @@ css-mode-tests--completions (should (member "body" completions)) (should-not (member "article" completions))))) +(ert-deftest css-test-rgb-parser () + (with-temp-buffer + (css-mode) + (dolist (input '("255, 0, 127" + "255, /* comment */ 0, 127" + "255 0 127" + "255, 0, 127, 0.75" + "255 0 127 / 0.75" + "100%, 0%, 50%" + "100%, 0%, 50%, 0.115" + "100% 0% 50%" + "100% 0% 50% / 0.115")) + (erase-buffer) + (save-excursion + (insert input ")")) + (should (equal (css--rgb-color) "#ff007f"))))) + +(ert-deftest css-test-hsl-parser () + (with-temp-buffer + (css-mode) + (dolist (input '("0, 100%, 50%" + "0 100% 50%" + "0 /* two */ /* comments */100% 50%" + "0, 100%, 50%, 0.75" + "0 100% 50% / 0.75" + "0deg 100% 50%" + "360deg 100% 50%" + "0rad, 100%, 50%, 0.115" + "0grad, 100%, 50%, 0.115" + "1turn 100% 50% / 0.115")) + (erase-buffer) + (save-excursion + (insert input ")")) + (should (equal (css--hsl-color) "#ff0000"))))) + (provide 'css-mode-tests) ;;; css-mode-tests.el ends here --=-=-= Content-Type: text/plain Tom In GNU Emacs 25.1.90.1 (x86_64-unknown-linux-gnu, GTK+ Version 3.20.9) of 2016-12-20 built on bapiya Repository revision: 88cdf14b37a7344bb266e94512485e3cc738c23d Windowing system distributor 'Fedora Project', version 11.0.11901000 System Description: Fedora release 25 (Twenty Five) Configured using: 'configure --prefix=/home/tromey/Emacs/install/ --with-modules' Configured features: XPM JPEG TIFF GIF PNG RSVG IMAGEMAGICK SOUND GPM DBUS GCONF GSETTINGS NOTIFY LIBSELINUX GNUTLS LIBXML2 FREETYPE M17N_FLT LIBOTF XFT ZLIB TOOLKIT_SCROLL_BARS GTK3 X11 MODULES Important settings: value of $LANG: en_US.utf8 value of $XMODIFIERS: @im=ibus locale-coding-system: utf-8-unix Major mode: VC dir Minor modes in effect: diff-auto-refine-mode: t which-function-mode: t erc-services-mode: t erc-list-mode: t erc-menu-mode: t erc-autojoin-mode: t erc-ring-mode: t erc-networks-mode: t erc-pcomplete-mode: t erc-track-mode: t erc-match-mode: t erc-netsplit-mode: t erc-hl-nicks-mode: t erc-button-mode: t erc-fill-mode: t erc-stamp-mode: t erc-irccontrols-mode: t erc-noncommands-mode: t erc-move-to-prompt-mode: t erc-readonly-mode: t savehist-mode: t tooltip-mode: t global-eldoc-mode: t electric-indent-mode: t mouse-wheel-mode: t file-name-shadow-mode: t global-font-lock-mode: t font-lock-mode: t auto-composition-mode: t auto-encryption-mode: t auto-compression-mode: t buffer-read-only: t column-number-mode: t line-number-mode: t transient-mark-mode: t Recent messages: Saving file /tmp/r.css... Wrote /tmp/r.css FONTIFY 30 530 FONTIFY 530 1030 Mark set [2 times] Saving file /home/tromey/Emacs/trunk/test/lisp/textmodes/css-mode-tests.el... Wrote /home/tromey/Emacs/trunk/test/lisp/textmodes/css-mode-tests.el When done with a buffer, type C-x # Hiding up-to-date and ignored items When done with a buffer, type C-x # Load-path shadows: /home/tromey/.emacs.d/elpa/erc-hl-nicks-1.3.2/erc-hl-nicks hides /home/tromey/.emacs.d/elpa/erc-hl-nicks-1.3.1/erc-hl-nicks /home/tromey/.emacs.d/elpa/erc-hl-nicks-1.3.2/erc-hl-nicks-autoloads hides /home/tromey/.emacs.d/elpa/erc-hl-nicks-1.3.1/erc-hl-nicks-autoloads /home/tromey/.emacs.d/elpa/erc-hl-nicks-1.3.2/erc-hl-nicks-pkg hides /home/tromey/.emacs.d/elpa/erc-hl-nicks-1.3.1/erc-hl-nicks-pkg /home/tromey/.emacs.d/elpa/bubbles-0.5/bubbles hides /home/tromey/Emacs/install/share/emacs/25.1.90/lisp/play/bubbles Features: (autoload cus-edit descr-text vc-annotate edebug xref project gnus-draft bug-reference view tcl web-mode lisp-mnt url-handlers log-view url-http url-gw url-auth eww url-queue gnus-html xml url-cache mm-url url url-proxy url-privacy url-expand url-methods url-history url-cookie url-domsuf smerge-mode shadow emacsbug log-edit pcvs-util whitespace idutils derived noutline outline find-dired bbdb-sc supercite regi css-mode smie js sgml-mode json map cc-mode cc-fonts cc-guess cc-menus cc-cmds apropos debug dabbrev eieio-opt speedbar sb-image ezimage dframe find-func copyright misearch multi-isearch add-log jka-compr bbdb-message vc-mtn vc-hg mailalias mail-hist nnir vc-bzr vc-src vc-sccs vc-svn vc-cvs vc-rcs vc-git diff-mode easy-mmode term/xterm xterm flow-fill shr-color url-util url-parse url-vars shr dom subr-x browse-url mm-archive sort smiley gnus-cite gnus-async gnus-bcklg mail-extr qp gnus-ml disp-table gnus-topic nndraft nnmh nnfolder utf-7 bbdb-gnus bbdb-mua bbdb-com crm network-stream nsm starttls gnus-agent gnus-srvr gnus-score score-mode nnvirtual gnus-msg nntp gnus-cache gnus-registry registry eieio-compat eieio-base gnus-art mm-uu mml2015 mm-view mml-smime smime dig mailcap gnus-sum gnus-group gnus-undo smtpmail sendmail gnus-start gnus-cloud nnimap nnmail mail-source tls gnutls utf7 netrc nnoo parse-time gnus-spec gnus-int gnus-range message idna dired rfc822 mml mml-sec epg mm-decode mm-bodies mm-encode mail-parse rfc2231 rfc2047 rfc2045 ietf-drums mailabbrev gmm-utils mailheader gnus-win gnus gnus-ems nnheader mail-utils flyspell ispell diminish edmacro kmacro projectile grep compile ibuf-ext ibuffer dash appt diary-lib diary-loaddefs cal-menu calendar cal-loaddefs which-func imenu minimap autorevert filenotify cus-start cus-load status erc-services erc-list erc-menu erc-join erc-ring erc-networks erc-pcomplete pcomplete erc-track erc-match erc-netsplit erc-hl-nicks color erc-button erc-fill erc-stamp wid-edit erc-goodies erc erc-backend erc-compat format-spec auth-source eieio gnus-util mm-util help-fns mail-prsvr password-cache thingatpt pp warnings advice vc-dir ewoc vc vc-dispatcher cc-styles cc-align cc-engine cc-vars cc-defs bbdb bbdb-site timezone ange-ftp comint ansi-color ring server savehist finder-inf dwarf-mode-autoloads gdb-shell-autoloads eieio-core lisppaste-autoloads pydoc-info-autoloads info-look cl-seq cl-macs cl weblogger-autoloads info package epg-config seq byte-opt gv bytecomp byte-compile cl-extra help-mode easymenu cconv cl-loaddefs pcase cl-lib bbdb-loaddefs time-date mule-util tooltip eldoc electric uniquify ediff-hook vc-hooks lisp-float-type mwheel x-win term/common-win x-dnd tool-bar dnd fontset image regexp-opt fringe tabulated-list newcomment elisp-mode lisp-mode prog-mode register page menu-bar rfn-eshadow timer select scroll-bar mouse jit-lock font-lock syntax facemenu font-core frame cl-generic cham georgian utf-8-lang misc-lang vietnamese tibetan thai tai-viet lao korean japanese eucjp-ms cp51932 hebrew greek romanian slovak czech european ethiopic indian cyrillic chinese charscript case-table epa-hook jka-cmpr-hook help simple abbrev minibuffer cl-preloaded nadvice loaddefs button faces cus-face macroexp files text-properties overlay sha1 md5 base64 format env code-pages mule custom widget hashtable-print-readable backquote dbusbind inotify dynamic-setting system-font-setting font-render-setting move-toolbar gtk x-toolkit x multi-tty make-network-process emacs) Memory information: ((conses 16 1974680 547097) (symbols 48 127411 61) (miscs 40 27623 7735) (strings 32 525621 148753) (string-bytes 1 13196826) (vectors 16 107759) (vector-slots 8 2410562 185496) (floats 8 874 1587) (intervals 56 200301 17468) (buffers 976 180) (heap 1024 305851 47738)) --=-=-=--