Quantcast

bug#25525: 25.1.90; add color highlighting to css mode

classic Classic list List threaded Threaded
30 messages Options
12
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

bug#25525: 25.1.90; add color highlighting to css mode

Tom Tromey-4

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.


commit 2b873579a2b821c073826eed5bc199216fbc14ae
Author: Tom Tromey <[hidden email]>
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



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))
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

bug#25525: 25.1.90; add color highlighting to css mode

Eli Zaretskii
> From: Tom Tromey <[hidden email]>
> Date: Wed, 25 Jan 2017 01:06:48 -0700
>
> 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.

Thanks.

I wonder whether it would make sense to reuse some existing data and
code here?

For example, tty-colors.el already has a (longer) list of colors with
suitable RGB values, so perhaps we should simply add the few missing
ones to that list, and then use that for CSS?

As another example, tty-colors.el also includes code for parsing and
converting color values (although perhaps not all of the formats you
support in your code).

As yet another example of existing functionality that you could
perhaps reuse, there's the :distant-foreground attribute of a face
that might help you with the issue of color contrast.

If you already considered all those and decided not to use them, could
you tell why?



Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

bug#25525: 25.1.90; add color highlighting to css mode

Glenn Morris-3
In reply to this post by Tom Tromey-4
Tom Tromey wrote:

> +(require 'cl-lib)
> +(require 'cl-macs)
> +(require 'color)

Don't explicitly load cl-macs. It's an internal part of cl-lib, and
loading cl-lib is all you need.



Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

bug#25525: 25.1.90; add color highlighting to css mode

Tom Tromey-4
In reply to this post by Eli Zaretskii
>>>>> "Eli" == Eli Zaretskii <[hidden email]> writes:

Eli> For example, tty-colors.el already has a (longer) list of colors with
Eli> suitable RGB values, so perhaps we should simply add the few missing
Eli> ones to that list, and then use that for CSS?

CSS specifies the named colors that are available -- so the list in
tty-colors.el would not be correct.

Eli> As another example, tty-colors.el also includes code for parsing and
Eli> converting color values (although perhaps not all of the formats you
Eli> support in your code).

Here too the new code supports what CSS specifies.  It's important to
the CSS developer that the Emacs mode follow CSS as precisely as
possible; using other formats supplied by tty-colors.el would result in
code that is invalid CSS.

I think the only area of overlap is the #RRGGBB hex syntax, but that is
so trivial as not to need any sharing.

Eli> As yet another example of existing functionality that you could
Eli> perhaps reuse, there's the :distant-foreground attribute of a face
Eli> that might help you with the issue of color contrast.

It seemed simpler to always set the :foreground, but I can experiment
with :distant-foreground instead.

Tom



Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

bug#25525: 25.1.90; add color highlighting to css mode

Simen Heggestøyl-2
In reply to this post by Tom Tromey-4
Thanks Tom, this looks to be a very nice addition to CSS mode!

Some comments:

- It should be possible to disable it. I'm not sure whether it should
  be turned on by default or not, though I'd think that most CSS
  developers will find it useful.

- Selectors that happen to contain the name of a color are
  highlighted. The 'red' part of '.centered-button' is highlighted in
  red, for instance.

- The same goes for variable names in both CSS and SCSS.

- Property names are also highlighted, for instance in the case of the
  'white-space' property.

- There's a stray call to 'message' in the beginning of
  'css--fontify-colors'.

- The error message in 'css--compute-color' should be capitalized.

- I think it deserves a NEWS entry.

-- Simen




Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

bug#25525: 25.1.90; add color highlighting to css mode

Tom Tromey-4
Simen> - It should be possible to disable it. I'm not sure whether it should
Simen>  be turned on by default or not, though I'd think that most CSS
Simen>  developers will find it useful.

I can add a defcustom.

Simen> - Selectors that happen to contain the name of a color are
Simen>  highlighted. The 'red' part of '.centered-button' is highlighted in
Simen>  red, for instance.

Thanks for noticing this.
I am sure I had \b or \< or something in the regexp earlier but I must
have dropped it.  I'll fix this up.

Tom



Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

bug#25525: 25.1.90; add color highlighting to css mode

Tom Tromey-4
In reply to this post by Simen Heggestøyl-2
Simen> Thanks Tom, this looks to be a very nice addition to CSS mode!
Simen> Some comments:
[...]

I think this version addresses all the review comments.

This necessitated changing the syntax table a bit.  Now things like
"#red" appear as a symbol, but "<red" appears as punctuation followed by
a word.

Tom


commit c544f57e5102fb19ab06bea6eb2cfeda5b839890
Author: Tom Tromey <[hidden email]>
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.
    (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): New tests.
    * etc/NEWS: Add entry.

diff --git a/etc/NEWS b/etc/NEWS
index ca66df6..73ef1de 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -603,6 +603,11 @@ HTML tags, classes and IDs using the 'completion-at-point' command.
 Completion candidates for HTML classes and IDs are retrieved from open
 HTML mode buffers.
 
+---
+*** 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 c81c3f6..0f6c996 100644
--- a/lisp/textmodes/css-mode.el
+++ b/lisp/textmodes/css-mode.el
@@ -32,6 +32,8 @@
 
 ;;; Code:
 
+(require 'cl-lib)
+(require 'color)
 (require 'seq)
 (require 'sgml-mode)
 (require 'smie)
@@ -453,8 +455,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 +657,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")
@@ -614,11 +736,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))
 
 (eval-and-compile
@@ -726,6 +860,206 @@ 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) '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.
+   "\\(\\_<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")))
+
+(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-colors (start end)
+  "Fontify CSS colors between START and END.
+START and END are buffer positions.
+This function is used via `jit-lock-register'."
+  (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 color)
+                                     :box '(:line-width -1)))))))))))))
+
 (defcustom css-indent-offset 4
   "Basic size of one indentation step."
   :version "22.2"
@@ -945,6 +1279,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
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

bug#25525: 25.1.90; add color highlighting to css mode

Tom Tromey-4
In reply to this post by Tom Tromey-4
>>>>> "Tom" == Tom Tromey <[hidden email]> writes:

Tom> It seemed simpler to always set the :foreground, but I can experiment
Tom> with :distant-foreground instead.

I gave this a try.  I think the code in this patch works better than
:distant-foreground.

One case where it is better is the CSS color "purple", aka #800080.

In my theme the foreground color is black.  My patch picks white as the
foreground, but the Emacs chooses not to use a distant-foreground, but
rather keep a black foreground.

You can compare these two cases by evalling:

(progn
  (insert (propertize
           "hello\n"
           'font-lock-face '(:background "#800080" :foreground "black"
                                         :distant-foreground "white")))
  (insert (propertize
           "hello\n"
           'font-lock-face '(:background "#800080" :foreground "white"))))

I find the latter much more readable.

Tom



Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

bug#25525: 25.1.90; add color highlighting to css mode

Eli Zaretskii
> From: Tom Tromey <[hidden email]>
> Cc: Eli Zaretskii <[hidden email]>,  [hidden email]
> Date: Wed, 25 Jan 2017 16:34:00 -0700
>
> >>>>> "Tom" == Tom Tromey <[hidden email]> writes:
>
> Tom> It seemed simpler to always set the :foreground, but I can experiment
> Tom> with :distant-foreground instead.
>
> I gave this a try.  I think the code in this patch works better than
> :distant-foreground.
>
> One case where it is better is the CSS color "purple", aka #800080.
>
> In my theme the foreground color is black.  My patch picks white as the
> foreground, but the Emacs chooses not to use a distant-foreground, but
> rather keep a black foreground.
>
> You can compare these two cases by evalling:
>
> (progn
>   (insert (propertize
>   "hello\n"
>   'font-lock-face '(:background "#800080" :foreground "black"
> :distant-foreground "white")))
>   (insert (propertize
>   "hello\n"
>   'font-lock-face '(:background "#800080" :foreground "white"))))
>
> I find the latter much more readable.

If all you need is to choose either black or white as the foreground
color, then :distant-foreground is indeed not for you.  Still, I'd
suggest to use color-distance rather than to invent a new metric.  Or
maybe just always use the color that is complementary to the
background color, as black and white seem arbitrary to me.

Thanks.



Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

bug#25525: 25.1.90; add color highlighting to css mode

Tom Tromey-4
Eli> If all you need is to choose either black or white as the foreground
Eli> color, then :distant-foreground is indeed not for you.  Still, I'd
Eli> suggest to use color-distance rather than to invent a new metric.  Or
Eli> maybe just always use the color that is complementary to the
Eli> background color, as black and white seem arbitrary to me.

I will test with complementary colors, but I am not sure those are
always as visible.  Black and white are arbitrary, but at least they
work ok.  I'm not sure there's a good way to use color distance to get
the desired effect; if there were, surely Emacs core would be doing that
already.

Tom



Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

bug#25525: 25.1.90; add color highlighting to css mode

Tom Tromey-4
In reply to this post by Eli Zaretskii
Eli> Still, I'd suggest to use color-distance rather than to invent a
Eli> new metric.

BTW I wanted to mention, I didn't invent this metric, it's well-known:

https://en.wikipedia.org/wiki/Luma_(video)

Tom



Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

bug#25525: 25.1.90; add color highlighting to css mode

Simen Heggestøyl-2
In reply to this post by Tom Tromey-4
On Thu, Jan 26, 2017 at 12:24 AM, Tom Tromey <[hidden email]> wrote:
> Simen> Thanks Tom, this looks to be a very nice addition to CSS mode!
> Simen> Some comments:
> [...]
>
> I think this version addresses all the review comments.

Thanks Tom, that seems to fix all the issues I mentioned.

I've come across one new problem when testing with the newest patch:
Sometimes, in buffers that are long enough to scroll in, some colors
aren't fontified until the line that contains the color is edited.

I'm able to reproduce it in
https://www.gnu.org/software/emacs/layout.css for instance, by opening
the file and searching for "#".

-- Simen




Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

bug#25525: 25.1.90; add color highlighting to css mode

Eli Zaretskii
In reply to this post by Tom Tromey-4
> From: Tom Tromey <[hidden email]>
> Cc: Tom Tromey <[hidden email]>,  [hidden email]
> Date: Thu, 26 Jan 2017 10:13:33 -0700
>
> I'm not sure there's a good way to use color distance to get the
> desired effect; if there were, surely Emacs core would be doing that
> already.

I thought about something like

  (if (> (color-distance color "black") 292485) "black" "white")

I think this is the equivalent of your test.

(One reason for color-distance being unused could be that it isn't
mentioned in the ELisp manual.)



Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

bug#25525: 25.1.90; add color highlighting to css mode

Tom Tromey-4
>>>>> "Eli" == Eli Zaretskii <[hidden email]> writes:

Eli> I thought about something like
Eli>   (if (> (color-distance color "black") 292485) "black" "white")
Eli> I think this is the equivalent of your test.

I tried this.  In particular with the patch applied, I did this in a
temporary (fundamental-mode) buffer:

(mapcar
 (lambda (c)
   (insert
    (propertize (car c) 'font-lock-face
                (list :background (cdr c)
                      :foreground (css--contrasty-color (cdr c))))
    " "
    (propertize (car c) 'font-lock-face
                (list :background (cdr c)
                      :foreground
                      (if (> (color-distance (cdr c) "black") 292485)
                          "black" "white")))
    "\n"))
 css--color-map)


... then I looked at all the pairs.

For the most part I think they are all ok.  Occasionally color-distance
picks a different color, sometimes a mildly worse one IMO, though at
least in one case ("grey") a mildly better one.

Anyway, I think it's good enough, and if someone trips across a
difficult case and files a bug, we can revisit it at that time.

Tom



Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

bug#25525: 25.1.90; add color highlighting to css mode

Tom Tromey-4
In reply to this post by Simen Heggestøyl-2
>>>>> "Simen" == Simen Heggestøyl <[hidden email]> 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.

I've seen weird behavior too but haven't debugged it yet.  Maybe
css-mode needs to set font-lock-fontify-region-function instead.  I am
not sure.

Tom



Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

bug#25525: 25.1.90; add color highlighting to css mode

Tom Tromey-4
In reply to this post by Simen Heggestøyl-2
>>>>> "Simen" == Simen Heggestøyl <[hidden email]> 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 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 19746c6..a1e17b4 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)
@@ -455,8 +457,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)
@@ -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 ?= "." st)
+    (modify-syntax-entry ?? "." st)
     st))
 
 (defvar css-mode-map
@@ -734,6 +868,201 @@ 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) '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.
+   "\\(\\_<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-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."
+  (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 color)
+                                     :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))
 
diff --git a/test/lisp/textmodes/css-mode-tests.el b/test/lisp/textmodes/css-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 @@
 
   ;; 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."
@@ -233,5 +233,40 @@ 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")))))
+
 (provide 'css-mode-tests)
 ;;; css-mode-tests.el ends here



Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

bug#25525: 25.1.90; add color highlighting to css mode

Simen Heggestøyl-2
On Sat, Feb 11, 2017 at 4:17 PM, Tom Tromey <[hidden email]> wrote:

>>>>>>  "Simen" == Simen Heggestøyl <[hidden email]> 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

Hi, Tom.

Unfortunately I'm still experiencing the bug with that version too.

-- Simen




Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

bug#25525: 25.1.90; add color highlighting to css mode

Tom Tromey-4
Simen> Unfortunately I'm still experiencing the bug with that version
Simen> too.

I was able to reproduce the problem and I think I found the bug:
css--fontify-region was not respecting any region extension done by
font-lock-default-fontify-region, but this is what extended
fontification to include the whole line containing point when searching.

Please try this version.

Tom


diff --git a/etc/NEWS b/etc/NEWS
index a8db54c..ae2ac3e 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -668,6 +668,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 7a9454f..8cec7ca 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)
@@ -455,8 +457,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)
@@ -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 ?= "." st)
+    (modify-syntax-entry ?? "." st)
     st))
 
 (defvar css-mode-map
@@ -735,6 +869,206 @@ 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) '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.
+   "\\(\\_<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-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"
@@ -1001,6 +1335,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..5fa4ae2 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,40 @@ 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")))))
+
 (provide 'css-mode-tests)
 ;;; css-mode-tests.el ends here
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

bug#25525: 25.1.90; add color highlighting to css mode

Simen Heggestøyl-2
On Sat, Mar 4, 2017 at 6:55 PM, Tom Tromey <[hidden email]> wrote:
> I was able to reproduce the problem and I think I found the bug:
> css--fontify-region was not respecting any region extension done by
> font-lock-default-fontify-region, but this is what extended
> fontification to include the whole line containing point when
> searching.
>
> Please try this version.

Nice, that seems to have fixed the bug!

I found another one: in SCSS mode, when using the `rgba` function
where one argument is a variable, subsequent colors aren't
highlighted. An example:

  body {
      color: #ddd;
      color: rgba($color-var, 1);
      color: #abc;  // Not highlighted
  }

There is also a test failure when you rebase the patch on the latest
master due to the changes in 7b00e956b4. It can be fixed by passing 2
as the last argument to `color-rgb-to-hex' in `css--hsl-color'.

-- Simen




Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

bug#25525: 25.1.90; add color highlighting to css mode

Tom Tromey-4
>>>>> "Simen" == Simen Heggestøyl <[hidden email]> writes:

Simen> I found another one: in SCSS mode, when using the `rgba` function
Simen> where one argument is a variable, subsequent colors aren't
Simen> highlighted. An example:

Thank you.  This was due to using the wrong block name in a call to
cl-return-from.  I must have changed the function name and forgotten
about this bit.  Anyway, apply the appended patch on top of what you've
got and please try to find more bugs :)

Simen> There is also a test failure when you rebase the patch on the latest
Simen> master due to the changes in 7b00e956b4. It can be fixed by passing 2
Simen> as the last argument to `color-rgb-to-hex' in `css--hsl-color'.

Thanks, I'll apply this to my patch.

Tom

diff --git a/lisp/textmodes/css-mode.el b/lisp/textmodes/css-mode.el
index 8cec7ca..7af9fdd 100644
--- a/lisp/textmodes/css-mode.el
+++ b/lisp/textmodes/css-mode.el
@@ -900,7 +900,7 @@ css--color-skip-blanks
     (while (< iter 4)
       (css--color-skip-blanks)
       (unless (looking-at css--number-or-percent-regexp)
- (cl-return-from css--css-4-rgb nil))
+ (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)))



12
Loading...