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: Sat, 11 Feb 2017 08:17:21 -0700 Message-ID: <87efz469ku.fsf@tromey.com> References: <87mvefh8br.fsf@tromey.com> <1485375833.1960.0@smtp.gmail.com> <1485455113.4245.1@smtp.gmail.com> NNTP-Posting-Host: blaine.gmane.org Mime-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: quoted-printable X-Trace: blaine.gmane.org 1486826294 13824 195.159.176.226 (11 Feb 2017 15:18:14 GMT) X-Complaints-To: usenet@blaine.gmane.org NNTP-Posting-Date: Sat, 11 Feb 2017 15:18:14 +0000 (UTC) User-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.1.91 (gnu/linux) Cc: 25525@debbugs.gnu.org, Tom Tromey To: Simen =?UTF-8?Q?Heggest=C3=B8yl?= Original-X-From: bug-gnu-emacs-bounces+geb-bug-gnu-emacs=m.gmane.org@gnu.org Sat Feb 11 16:18:09 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 1ccZR9-00036m-Vg for geb-bug-gnu-emacs@m.gmane.org; Sat, 11 Feb 2017 16:18:08 +0100 Original-Received: from localhost ([::1]:48666 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1ccZRF-0007Rr-G0 for geb-bug-gnu-emacs@m.gmane.org; Sat, 11 Feb 2017 10:18:13 -0500 Original-Received: from eggs.gnu.org ([2001:4830:134:3::10]:32947) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1ccZR8-0007Rl-9h for bug-gnu-emacs@gnu.org; Sat, 11 Feb 2017 10:18:09 -0500 Original-Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1ccZR4-000114-TW for bug-gnu-emacs@gnu.org; Sat, 11 Feb 2017 10:18:06 -0500 Original-Received: from debbugs.gnu.org ([208.118.235.43]:37615) by eggs.gnu.org with esmtps (TLS1.0:RSA_AES_128_CBC_SHA1:16) (Exim 4.71) (envelope-from ) id 1ccZR4-000108-Kq for bug-gnu-emacs@gnu.org; Sat, 11 Feb 2017 10:18:02 -0500 Original-Received: from Debian-debbugs by debbugs.gnu.org with local (Exim 4.84_2) (envelope-from ) id 1ccZR4-0004fr-9Y for bug-gnu-emacs@gnu.org; Sat, 11 Feb 2017 10:18:02 -0500 X-Loop: help-debbugs@gnu.org Resent-From: Tom Tromey Original-Sender: "Debbugs-submit" Resent-CC: bug-gnu-emacs@gnu.org Resent-Date: Sat, 11 Feb 2017 15:18:02 +0000 Resent-Message-ID: Resent-Sender: help-debbugs@gnu.org X-GNU-PR-Message: followup 25525 X-GNU-PR-Package: emacs X-GNU-PR-Keywords: patch Original-Received: via spool by 25525-submit@debbugs.gnu.org id=B25525.148682626717949 (code B ref 25525); Sat, 11 Feb 2017 15:18:02 +0000 Original-Received: (at 25525) by debbugs.gnu.org; 11 Feb 2017 15:17:47 +0000 Original-Received: from localhost ([127.0.0.1]:35814 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1ccZQo-0004fP-8w for submit@debbugs.gnu.org; Sat, 11 Feb 2017 10:17:46 -0500 Original-Received: from gproxy7-pub.mail.unifiedlayer.com ([70.40.196.235]:49207) by debbugs.gnu.org with smtp (Exim 4.84_2) (envelope-from ) id 1ccZQl-0004fB-It for 25525@debbugs.gnu.org; Sat, 11 Feb 2017 10:17:44 -0500 Original-Received: (qmail 6744 invoked by uid 0); 11 Feb 2017 15:17:30 -0000 Original-Received: from unknown (HELO cmgw2) (10.0.90.83) by gproxy7.mail.unifiedlayer.com with SMTP; 11 Feb 2017 15:17:30 -0000 Original-Received: from box522.bluehost.com ([74.220.219.122]) by cmgw2 with id jTHS1u0012f2jeq01THVXo; Sat, 11 Feb 2017 08:17:30 -0700 X-Authority-Analysis: v=2.1 cv=H5NInYoi 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=IkcTkHD0fZMA:10 a=n2v9WMKugxEA:10 a=pGLkceISAAAA:8 a=O9wxBRIL8WYq-iCCro0A:9 a=ObKISWM53QNM9CDf:21 a=_x5vgm7SCCLjpacH:21 a=QEXdDO2ut3YA:10 a=6kGIvZw6iX1k4Y-7sg4_:22 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=tromey.com; s=default; h=Content-Transfer-Encoding:Content-Type:MIME-Version:Message-ID: In-Reply-To:Date:References:Subject:Cc:To:From:Sender:Reply-To:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:List-Id:List-Help:List-Unsubscribe:List-Subscribe: List-Post:List-Owner:List-Archive; bh=+wD7FwQ1VrwHXTqXcXExj3CH+C3QonzCrll5G/C0PEQ=; b=jRVLGfrKYPrbTcYCYqwNFoUFjw ssR6BjlViovJ8ZKOUfJPepgAC9rEylBla+SZxXwnvzqWDzd0X6dfemdlKpD6mpGvGm7uDcyOFd96j /6JsKrwo7A8JXgNlGIISkBolS; Original-Received: from 75-171-188-196.hlrn.qwest.net ([75.171.188.196]:49926 helo=bapiya) by box522.bluehost.com with esmtpsa (TLSv1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.87) (envelope-from ) id 1ccZQT-0000D9-NI; Sat, 11 Feb 2017 08:17:25 -0700 X-Attribution: Tom In-Reply-To: <1485455113.4245.1@smtp.gmail.com> ("Simen \=\?utf-8\?Q\?Heggest\?\= \=\?utf-8\?Q\?\=C3\=B8yl\=22's\?\= message of "Thu, 26 Jan 2017 19:25:13 +0100") 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 - debbugs.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: 75.171.188.196 X-Exim-ID: 1ccZQT-0000D9-NI X-Source: X-Source-Args: X-Source-Dir: X-Source-Sender: 75-171-188-196.hlrn.qwest.net (bapiya) [75.171.188.196]:49926 X-Source-Auth: tom+tromey.com X-Email-Count: 2 X-Source-Cap: ZWx5bnJvYmk7ZWx5bnJvYmk7Ym94NTIyLmJsdWVob3N0LmNvbQ== 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:129242 Archived-At: >>>>> "Simen" =3D=3D Simen Heggest=C3=B8yl writes: Simen> I've come across one new problem when testing with the newest patch: Simen> Sometimes, in buffers that are long enough to scroll in, some colors Simen> aren't fontified until the line that contains the color is edited. Could you try this version? Tom diff --git a/etc/NEWS b/etc/NEWS index da0b538..4287387 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -638,6 +638,11 @@ pseudo-element, with the default being guessed from co= ntext). By default the information is looked up on the Mozilla Developer Network, but this can be customized using 'css-lookup-url-format'. =20 +--- +*** CSS colors are fontified using the color they represent as the +background. For instance, #ff0000 would be fontified with a red +background. + +++ ** Emacs now supports character name escape sequences in character and string literals. The syntax variants \N{character name} and diff --git a/lisp/textmodes/css-mode.el b/lisp/textmodes/css-mode.el index 19746c6..a1e17b4 100644 --- a/lisp/textmodes/css-mode.el +++ b/lisp/textmodes/css-mode.el @@ -33,6 +33,8 @@ ;;; Code: =20 (require 'eww) +(require 'cl-lib) +(require 'color) (require 'seq) (require 'sgml-mode) (require 'smie) @@ -455,8 +457,157 @@ css-property-ids (mapcar #'car css-property-alist) "Identifiers for properties.") =20 +(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) @@ -508,36 +659,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") @@ -616,11 +738,23 @@ css-mode-syntax-table (modify-syntax-entry ?\[ "(]" st) (modify-syntax-entry ?\] ")[" st) ;; Special chars that sometimes come at the beginning of words. - (modify-syntax-entry ?@ "'" st) - ;; (modify-syntax-entry ?: "'" st) - (modify-syntax-entry ?# "'" st) + ;; We'll treat them as symbol constituents. + (modify-syntax-entry ?@ "_" st) + (modify-syntax-entry ?# "_" st) + (modify-syntax-entry ?. "_" st) ;; Distinction between words and symbols. (modify-syntax-entry ?- "_" st) + + (modify-syntax-entry ?! "." st) + (modify-syntax-entry ?$ "." st) + (modify-syntax-entry ?% "." st) + (modify-syntax-entry ?& "." st) + (modify-syntax-entry ?+ "." st) + (modify-syntax-entry ?, "." st) + (modify-syntax-entry ?< "." st) + (modify-syntax-entry ?> "." st) + (modify-syntax-entry ?=3D "." st) + (modify-syntax-entry ?? "." st) st)) =20 (defvar css-mode-map @@ -734,6 +868,201 @@ css-font-lock-keywords (defvar css-font-lock-defaults '(css-font-lock-keywords nil t)) =20 +(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 (=3D (skip-chars-forward ",/") 0) + (=3D 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) 'symbols) + "\\|" + ;; 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. + "\\(\\_ (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-distance'. +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." + ;; See bug#25525 for a discussion of this. + (if (> (color-distance name "black") 292485) + "black" "white")) + +(defcustom css-fontify-colors t + "Whether CSS colors should be fontified using the color as the backgroun= d. +When non-`nil', a text representing CSS color will be fontified +such that its background is the color itself. E.g., #ff0000 will +be fontified with a red background." + :version "26.1" + :group 'css + :type 'boolean + :safe 'booleanp) + +(defun css--fontify-region (start end &optional loudly) + "Fontify a CSS buffer between START and END. +START and END are buffer positions." + (font-lock-default-fontify-region start end loudly) + (when css-fontify-colors + (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 col= or) + :box '(:line-width -1))))))))))))) + (defcustom css-indent-offset 4 "Basic size of one indentation step." :version "22.2" @@ -988,6 +1317,7 @@ css-mode :backward-token #'css-smie--backward-token) (setq-local electric-indent-chars (append css-electric-keys electric-indent-chars)) + (setq-local font-lock-fontify-region-function #'css--fontify-region) (add-hook 'completion-at-point-functions #'css-completion-at-point nil 'local)) =20 diff --git a/test/lisp/textmodes/css-mode-tests.el b/test/lisp/textmodes/cs= s-mode-tests.el index 5372c37..a741484 100644 --- a/test/lisp/textmodes/css-mode-tests.el +++ b/test/lisp/textmodes/css-mode-tests.el @@ -58,7 +58,7 @@ =20 ;; Check that the `color' property doesn't cause infinite recursion ;; because it refers to the value class of the same name. - (should (=3D (length (css--property-values "color")) 147))) + (should (=3D (length (css--property-values "color")) 152))) =20 (ert-deftest css-test-property-value-cache () "Test that `css--property-value-cache' is in use." @@ -233,5 +233,40 @@ css-mode-tests--completions (save-excursion (insert (nth 1 item))) (should (equal (nth 2 item) (css--mdn-find-symbol)))))) =20 +(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