Subject: bug#29456: closed (Re: bug#29456: [PATCH] Add command for cycling between CSS color formats)

From: [email-masked] (GNU bug Tracking System)
Date: Sun, 17 Dec 2017 09:33:03 +0000

Your bug report

#29456: [PATCH] Add command for cycling between CSS color formats

which was filed against the emacs package, has been closed.

The explanation is attached below, along with your original report.
If you require more details, please reply to [email-masked].

-- 
29456: http://debbugs.gnu.org/cgi/bugreport.cgi?bug=29456
GNU Bug Tracking System
Contact [email-masked] with problems

Subject: Re: bug#29456: [PATCH] Add command for cycling between CSS color formats
From: Simen Heggestøyl <[email-masked]>
To: Eli Zaretskii <[email-masked]>
Cc: [email-masked], [email-masked], [email-masked]
Date: Sun, 17 Dec 2017 10:32:01 +0100
List-Id: <debbugs-submit.debbugs.gnu.org>

Good, thanks. I've installed the two patches.

-- Simen

On Sun, Dec 10, 2017 at 7:09 PM, Eli Zaretskii <[email-masked]> wrote:
> It's OK to put the css--hex-color bugfix on emacs-26, but then please
> remove it from the changeset you push to master, so that it could be
> merged from emacs-26.
> 
> Thanks.

Subject: [PATCH] Add command for cycling between CSS color formats
From: Simen Heggestøyl <[email-masked]>
To: [email-masked]
Cc: Tom Tromey <[email-masked]>, Stefan Monnier <[email-masked]>
Date: Sun, 26 Nov 2017 16:21:51 +0100
List-Id: <debbugs-submit.debbugs.gnu.org>

The attached patch adds a new command 'css-cycle-color-format' to CSS
mode, for cycling between color formats (e.g. "black" => "#000000" =>
"rgb(0, 0, 0)" => "black"), bound to 'C-c C-f'.

I'll install the patch after a while unless there are any comments.

-- Simen
>From 54f258b39822d7042c240fe6bbde63ccfb49e59d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Simen=20Heggest=C3=B8yl?= <[email-masked]>
Date: Sun, 17 Sep 2017 20:08:18 +0200
Subject: [PATCH] Add command for cycling between CSS color formats

* lisp/textmodes/css-mode.el (css-mode-map): Add keybinding for
`css-cycle-color-format'.
(css--web-color-to-4-dpc, css-named-color-to-hex)
(css-hex-to-rgb, css-rgb-to-named-color-or-hex): New functions.
(css-cycle-color-format): New command for cycling between color
formats.

* test/lisp/textmodes/css-mode-tests.el (css-test-web-color-to-4-dpc):
(css-test-named-color-to-hex, css-test-hex-to-rgb)
(css-test-rgb-to-named-color-or-hex, css-test-cycle-color-format): New
tests for the functions mentioned above.

* etc/NEWS: Mention the new command.
---
 etc/NEWS                              |  7 ++++
 lisp/textmodes/css-mode.el            | 79 ++++++++++++++++++++++++++++++++++-
 test/lisp/textmodes/css-mode-tests.el | 53 +++++++++++++++++++++++
 3 files changed, 138 insertions(+), 1 deletion(-)

diff --git a/etc/NEWS b/etc/NEWS
index c47ca42d27..ab9f285740 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -63,6 +63,13 @@ whether '"' is also replaced in 'electric-quote-mode'.  If non-nil,
 
 * Changes in Specialized Modes and Packages in Emacs 27.1
 
+** CSS mode
+
+---
+*** A new command 'css-cycle-color-format' for cycling between color
+formats (e.g. "black" => "#000000" => "rgb(0, 0, 0)") has been added,
+bound to 'C-c C-f'.
+
 ** Dired
 
 +++
diff --git a/lisp/textmodes/css-mode.el b/lisp/textmodes/css-mode.el
index 93ca36b08a..aafcf8ade1 100644
--- a/lisp/textmodes/css-mode.el
+++ b/lisp/textmodes/css-mode.el
@@ -32,12 +32,13 @@
 
 ;;; Code:
 
-(require 'eww)
 (require 'cl-lib)
 (require 'color)
+(require 'eww)
 (require 'seq)
 (require 'sgml-mode)
 (require 'smie)
+(require 'thingatpt)
 (eval-when-compile (require 'subr-x))
 
 (defgroup css nil
@@ -806,6 +807,7 @@ css-mode-syntax-table
 (defvar css-mode-map
   (let ((map (make-sparse-keymap)))
     (define-key map [remap info-lookup-symbol] 'css-lookup-symbol)
+    (define-key map "\C-c\C-f" 'css-cycle-color-format)
     map)
   "Keymap used in `css-mode'.")
 
@@ -1383,6 +1385,81 @@ css-completion-at-point
                       (progn (insert ": ;")
                              (forward-char -1))))))))))
 
+(defun css--web-color-to-4-dpc (hex)
+  "Convert HEX web color to four digits per component.
+Web colors use one or two digits per component for RGB hex
+values.  Convert the given color to four digits per component."
+  (let ((six-digits (= (length hex) 7)))
+    (apply
+     #'concat
+     `("#"
+       ,@(seq-mapcat
+          (apply-partially #'make-list (if six-digits 2 4))
+          (seq-partition (seq-drop hex 1) (if six-digits 2 1)))))))
+
+(defun css-named-color-to-hex ()
+  "Convert named color at point to hex format.
+Return non-nil if a conversion was made."
+  (save-excursion
+    (unless (or (looking-at css--colors-regexp)
+                (eq (char-before) ?#))
+      (backward-word))
+    (when (member (word-at-point) (mapcar #'car css--color-map))
+      (looking-at css--colors-regexp)
+      (let ((color (css--compute-color (point) (match-string 0))))
+        (replace-match color))
+      t)))
+
+(defun css-hex-to-rgb ()
+  "Convert hex color at point to RGB format.
+Return non-nil if a conversion was made."
+  (save-excursion
+    (unless (eq (char-after) ?#)
+      (backward-sexp))
+    (when-let* ((hex (when (looking-at css--colors-regexp)
+                       (css--compute-color (point) (match-string 0)))))
+      (seq-let (r g b)
+          (mapcar (lambda (x) (round (* x 255)))
+                  (color-name-to-rgb (css--web-color-to-4-dpc hex)))
+        (replace-match (format "rgb(%d, %d, %d)" r g b)))
+      t)))
+
+(defun css-rgb-to-named-color-or-hex ()
+  "Convert RGB color at point to a named color or hex format.
+Convert to a named color if the color at point has a name, else
+convert to hex format.  Return non-nil if a conversion was made."
+  (save-excursion
+    (when-let* ((open-paren-pos (nth 1 (syntax-ppss))))
+      (when (save-excursion
+              (goto-char open-paren-pos)
+              (looking-back "rgb" 3))
+        (goto-char (nth 1 (syntax-ppss)))))
+    (when (eq (char-before) ?\))
+      (backward-sexp))
+    (skip-chars-backward "rgb")
+    (when (looking-at css--colors-regexp)
+      (let* ((start (match-end 0))
+             (color (save-excursion
+                      (goto-char start)
+                      (css--compute-color start (match-string 0)))))
+        (when color
+          (kill-sexp)
+          (kill-sexp)
+          (let ((named-color (seq-find (lambda (x) (equal (cdr x) color))
+                                       css--color-map)))
+            (insert (if named-color (car named-color) color)))
+          t)))))
+
+(defun css-cycle-color-format ()
+  "Cycle the color at point between different formats.
+Supported formats are by name (if possible), hexadecimal, and
+RGB."
+  (interactive)
+  (or (css-named-color-to-hex)
+      (css-hex-to-rgb)
+      (css-rgb-to-named-color-or-hex)
+      (message "It doesn't look like a color at point")))
+
 ;;;###autoload
 (define-derived-mode css-mode prog-mode "CSS"
   "Major mode to edit Cascading Style Sheets (CSS).
diff --git a/test/lisp/textmodes/css-mode-tests.el b/test/lisp/textmodes/css-mode-tests.el
index 47cf5f9244..be1ed55e42 100644
--- a/test/lisp/textmodes/css-mode-tests.el
+++ b/test/lisp/textmodes/css-mode-tests.el
@@ -244,6 +244,59 @@ css-mode-tests--completions
       (should (member "body" completions))
       (should-not (member "article" completions)))))
 
+(ert-deftest css-test-web-color-to-4-dpc ()
+  (should (equal (css--web-color-to-4-dpc "#ffffff")
+                 (css--web-color-to-4-dpc "#fff")))
+  (should (equal (css--web-color-to-4-dpc "#aabbcc")
+                 (css--web-color-to-4-dpc "#abc")))
+  (should (equal (css--web-color-to-4-dpc "#fab")
+                 "#ffffaaaabbbb"))
+  (should (equal (css--web-color-to-4-dpc "#fafbfc")
+                 "#fafafbfbfcfc")))
+
+(ert-deftest css-test-named-color-to-hex ()
+  (dolist (item '(("black" "#000000")
+                  ("white" "#ffffff")
+                  ("salmon" "#fa8072")))
+    (with-temp-buffer
+      (css-mode)
+      (insert (nth 0 item))
+      (css-named-color-to-hex)
+      (should (equal (buffer-string) (nth 1 item))))))
+
+(ert-deftest css-test-hex-to-rgb ()
+  (dolist (item '(("#000" "rgb(0, 0, 0)")
+                  ("#000000" "rgb(0, 0, 0)")
+                  ("#fff" "rgb(255, 255, 255)")
+                  ("#ffffff" "rgb(255, 255, 255)")))
+    (with-temp-buffer
+      (css-mode)
+      (insert (nth 0 item))
+      (css-hex-to-rgb)
+      (should (equal (buffer-string) (nth 1 item))))))
+
+(ert-deftest css-test-rgb-to-named-color-or-hex ()
+  (dolist (item '(("rgb(0, 0, 0)" "black")
+                  ("rgb(255, 255, 255)" "white")
+                  ("rgb(255, 255, 240)" "ivory")
+                  ("rgb(18, 52, 86)" "#123456")))
+    (with-temp-buffer
+      (css-mode)
+      (insert (nth 0 item))
+      (css-rgb-to-named-color-or-hex)
+      (should (equal (buffer-string) (nth 1 item))))))
+
+(ert-deftest css-test-cycle-color-format ()
+  (with-temp-buffer
+    (css-mode)
+    (insert "black")
+    (css-cycle-color-format)
+    (should (equal (buffer-string) "#000000"))
+    (css-cycle-color-format)
+    (should (equal (buffer-string) "rgb(0, 0, 0)"))
+    (css-cycle-color-format)
+    (should (equal (buffer-string) "black"))))
+
 (ert-deftest css-mdn-symbol-guessing ()
   (dolist (item '(("@med" "ia" "@media")
                   ("@keyframes " "{" "@keyframes")
-- 
2.15.0