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: Thu, 20 Apr 2017 21:40:46 -0600 Message-ID: <874lxi5sxt.fsf@tromey.com> References: <87mvefh8br.fsf@tromey.com> <1485375833.1960.0@smtp.gmail.com> <1485455113.4245.1@smtp.gmail.com> <1487359774.1998.0@smtp.gmail.com> <1488653191.6153.0@smtp.gmail.com> <1488738524.13864.0@smtp.gmail.com> <871str3b48.fsf@tromey.com> <1490812056.14782.1@smtp.gmail.com> NNTP-Posting-Host: blaine.gmane.org Mime-Version: 1.0 Content-Type: multipart/mixed; boundary="=-=-=" X-Trace: blaine.gmane.org 1492746138 14349 195.159.176.226 (21 Apr 2017 03:42:18 GMT) X-Complaints-To: usenet@blaine.gmane.org NNTP-Posting-Date: Fri, 21 Apr 2017 03:42:18 +0000 (UTC) User-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.2 (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 Fri Apr 21 05:42:13 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 1d1PSW-0003Wt-Ej for geb-bug-gnu-emacs@m.gmane.org; Fri, 21 Apr 2017 05:42:12 +0200 Original-Received: from localhost ([::1]:56924 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1d1PSa-0002A3-Qa for geb-bug-gnu-emacs@m.gmane.org; Thu, 20 Apr 2017 23:42:16 -0400 Original-Received: from eggs.gnu.org ([2001:4830:134:3::10]:55691) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1d1PSQ-00028i-D2 for bug-gnu-emacs@gnu.org; Thu, 20 Apr 2017 23:42:09 -0400 Original-Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1d1PSM-0006xp-AQ for bug-gnu-emacs@gnu.org; Thu, 20 Apr 2017 23:42:06 -0400 Original-Received: from debbugs.gnu.org ([208.118.235.43]:33166) by eggs.gnu.org with esmtps (TLS1.0:RSA_AES_128_CBC_SHA1:16) (Exim 4.71) (envelope-from ) id 1d1PSM-0006xb-23 for bug-gnu-emacs@gnu.org; Thu, 20 Apr 2017 23:42:02 -0400 Original-Received: from Debian-debbugs by debbugs.gnu.org with local (Exim 4.84_2) (envelope-from ) id 1d1PSL-0006xz-SM for bug-gnu-emacs@gnu.org; Thu, 20 Apr 2017 23:42:01 -0400 X-Loop: help-debbugs@gnu.org Resent-From: Tom Tromey Original-Sender: "Debbugs-submit" Resent-CC: bug-gnu-emacs@gnu.org Resent-Date: Fri, 21 Apr 2017 03:42:01 +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.149274606826715 (code B ref 25525); Fri, 21 Apr 2017 03:42:01 +0000 Original-Received: (at 25525) by debbugs.gnu.org; 21 Apr 2017 03:41:08 +0000 Original-Received: from localhost ([127.0.0.1]:59598 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1d1PRT-0006wo-Eb for submit@debbugs.gnu.org; Thu, 20 Apr 2017 23:41:08 -0400 Original-Received: from gproxy10-pub.mail.unifiedlayer.com ([69.89.20.226]:35126 helo=gproxy10.mail.unifiedlayer.com) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1d1PRQ-0006vg-52 for 25525@debbugs.gnu.org; Thu, 20 Apr 2017 23:41:05 -0400 Original-Received: from cmgw3 (unknown [10.0.90.84]) by gproxy10.mail.unifiedlayer.com (Postfix) with ESMTP id 467781405A8 for <25525@debbugs.gnu.org>; Thu, 20 Apr 2017 21:40:53 -0600 (MDT) Original-Received: from box522.bluehost.com ([74.220.219.122]) by cmgw3 with id Argp1v0032f2jeq01rgsV2; Thu, 20 Apr 2017 21:40:53 -0600 X-Authority-Analysis: v=2.2 cv=VKStp5HX c=1 sm=1 tr=0 a=GsOEXm/OWkKvwdLVJsfwcA==:117 a=GsOEXm/OWkKvwdLVJsfwcA==:17 a=AzvcPWV-tVgA:10 a=pGLkceISAAAA:8 a=HfW9UNEZNpKdSpP3phQA:9 a=QEXdDO2ut3YA:10 a=zstS-IiYAAAA:8 a=WxUXrq8xl85NNRlDl-AA:9 a=-h-0T419LePNFRIQ:21 a=OBeojw1dOw5GutBD:21 a=6kGIvZw6iX1k4Y-7sg4_:22 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:In-Reply-To:Date: References:Subject:Cc:To:From:Sender:Reply-To:Content-Transfer-Encoding: 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=WQQ6MdcdpCZ/AWXBnzNUGjKLZJvmNeREqJwJQB1kzm8=; b=qYINw5gTknTrKT8IT5aOQNrgh9 bLtD7QtGjKND8OGLIUP8vAHE7thqyZlUzc+a8c9F21xap4/Hk/MQGEka/wZErj01G/BJLiCpfafIh OizEoiDXU9xvc0qSgqiNRwz2H; Original-Received: from 75-166-40-121.hlrn.qwest.net ([75.166.40.121]:54256 helo=bapiya) by box522.bluehost.com with esmtpsa (TLSv1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.87) (envelope-from ) id 1d1PRA-0005HP-PD; Thu, 20 Apr 2017 21:40:48 -0600 X-Attribution: Tom In-Reply-To: <1490812056.14782.1@smtp.gmail.com> ("Simen \=\?utf-8\?Q\?Heggest\?\= \=\?utf-8\?Q\?\=C3\=B8yl\=22's\?\= message of "Wed, 29 Mar 2017 20:27:36 +0200") 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.166.40.121 X-Exim-ID: 1d1PRA-0005HP-PD X-Source: X-Source-Args: X-Source-Dir: X-Source-Sender: 75-166-40-121.hlrn.qwest.net (bapiya) [75.166.40.121]:54256 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:131812 Archived-At: --=-=-= Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: quoted-printable >>>>> "Simen" =3D=3D Simen Heggest=C3=B8yl writes: Sorry about the delay on this. I've been busy with other things. Simen> Hm, I'm still seeing it here with the patch from March 4. Maybe Simen> that one is outdated? I wouldn't think so, but I'm attaching a rolled-up patch here. Simen> I think the names of mixins and placeholders are most important, bec= ause Simen> it's not unlikely for them to have colors for names. Examples of usa= ge: Ok, I can easily exclude @mixin and @extend. Simen> There's also maps, but I think they're less important if it's hard to Simen> solve: Simen> $foo: (bar: 1, black: 2); I'm not sure there's a good way to distinguish these. Let me know what you think. I appreciate you taking the time to try this out. FWIW I've been using this in my work. It works with mhtml-mode, so I've been seeing colors show up in Firefox mochitests... fun! Tom --=-=-= Content-Type: text/x-patch Content-Disposition: inline; filename=P Content-Description: latest patch commit 44c67a067d7759fb15af032416bce51714bb12eb 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--named-color, css--compute-color) (css--contrasty-color, css--fontify-colors) (css--fontify-region): New functions. (css-mode): Set font-lock-fontify-region-function. (css-mode-syntax-table): Set syntax on more characters. (css-fontify-colors): New defcustom. * test/lisp/textmodes/css-mode-tests.el (css-test-property-values): Update. (css-test-rgb-parser, css-test-hsl-parser) (css-test-named-color): New tests. * etc/NEWS: Add entry. diff --git a/etc/NEWS b/etc/NEWS index 7281827..2b0841d 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -719,6 +719,11 @@ pseudo-element, with the default being guessed from context). By default the information is looked up on the Mozilla Developer Network, but this can be customized using 'css-lookup-url-format'. +--- +*** 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 d4a5cfe..89c7cb9 100644 --- a/lisp/textmodes/css-mode.el +++ b/lisp/textmodes/css-mode.el @@ -33,6 +33,8 @@ ;;; Code: (require 'eww) +(require 'cl-lib) +(require 'color) (require 'seq) (require 'sgml-mode) (require 'smie) @@ -487,8 +489,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 colors 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) @@ -550,36 +701,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") @@ -663,11 +785,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 ?= "." st) + (modify-syntax-entry ?? "." st) st)) (defvar css-mode-map @@ -782,6 +916,218 @@ 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--rgb-color 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 + (nconc (apply #'color-hsl-to-rgb (nreverse result)) '(2)))))) + +(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--named-color (str) + "Check whether STR, seen at point, is CSS named color. +Returns STR if it is a valid color. Special care is taken +to exclude some SCSS contructs." + (when-let ((color (assoc str css--color-map))) + (save-excursion + ;; We still have the match from the caller of + ;; css--compute-color. + (goto-char (match-beginning 0)) + (forward-comment (- (point))) + (skip-chars-backward "@[:alpha:]") + (unless (looking-at-p "@\\(mixin\\|include\\)") + (cdr color))))) + +(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. + ((css--named-color match))))) + +(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 background. +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." + (let ((extended-region (font-lock-default-fontify-region start end loudly))) + (when css-fontify-colors + (when (and (consp extended-region) + (eq (car extended-region) 'jit-lock-bounds)) + (setq start (cadr extended-region)) + (setq end (cddr extended-region))) + (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)))))))))))) + extended-region)) + (defcustom css-indent-offset 4 "Basic size of one indentation step." :version "22.2" @@ -1048,6 +1394,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)) diff --git a/test/lisp/textmodes/css-mode-tests.el b/test/lisp/textmodes/css-mode-tests.el index d601f43..0e7e945 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." @@ -234,5 +234,48 @@ css-mode-tests--completions (save-excursion (insert (nth 1 item))) (should (equal (nth 2 item) (css--mdn-find-symbol)))))) +(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"))))) + +(ert-deftest css-test-named-color () + (dolist (text '("@mixin black" "@include black")) + (with-temp-buffer + (insert text) + (backward-word) + (should (and (looking-at "black") + (not (css--named-color "black"))))))) + (provide 'css-mode-tests) ;;; css-mode-tests.el ends here --=-=-=--