From mboxrd@z Thu Jan 1 00:00:00 1970 Path: news.gmane.org!.POSTED!not-for-mail From: Simen =?UTF-8?Q?Heggest=C3=B8yl?= Newsgroups: gmane.emacs.bugs Subject: bug#29456: [PATCH] Add command for cycling between CSS color formats Date: Sun, 10 Dec 2017 13:46:14 +0100 Message-ID: <1512909974.15208.0@smtp.gmail.com> References: <87lgiqmex1.fsf@gmail.com> NNTP-Posting-Host: blaine.gmane.org Mime-Version: 1.0 Content-Type: multipart/mixed; boundary="=-08ow+NF3IG0A/dGjfhVQ" X-Trace: blaine.gmane.org 1512910039 4817 195.159.176.226 (10 Dec 2017 12:47:19 GMT) X-Complaints-To: usenet@blaine.gmane.org NNTP-Posting-Date: Sun, 10 Dec 2017 12:47:19 +0000 (UTC) Cc: 29456@debbugs.gnu.org, tom@tromey.com, monnier@iro.umontreal.ca To: Simen =?UTF-8?Q?Heggest=C3=B8yl?= Original-X-From: bug-gnu-emacs-bounces+geb-bug-gnu-emacs=m.gmane.org@gnu.org Sun Dec 10 13:47:13 2017 Return-path: Envelope-to: geb-bug-gnu-emacs@m.gmane.org Original-Received: from lists.gnu.org ([208.118.235.17]) by blaine.gmane.org with esmtp (Exim 4.84_2) (envelope-from ) id 1eO10i-0000qn-HL for geb-bug-gnu-emacs@m.gmane.org; Sun, 10 Dec 2017 13:47:12 +0100 Original-Received: from localhost ([::1]:44423 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1eO10m-0001Qj-9L for geb-bug-gnu-emacs@m.gmane.org; Sun, 10 Dec 2017 07:47:16 -0500 Original-Received: from eggs.gnu.org ([2001:4830:134:3::10]:56534) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1eO10b-0001PF-Fn for bug-gnu-emacs@gnu.org; Sun, 10 Dec 2017 07:47:08 -0500 Original-Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1eO10Y-0004Jo-9W for bug-gnu-emacs@gnu.org; Sun, 10 Dec 2017 07:47:05 -0500 Original-Received: from debbugs.gnu.org ([208.118.235.43]:45876) by eggs.gnu.org with esmtps (TLS1.0:RSA_AES_128_CBC_SHA1:16) (Exim 4.71) (envelope-from ) id 1eO10Y-0004Jf-47 for bug-gnu-emacs@gnu.org; Sun, 10 Dec 2017 07:47:02 -0500 Original-Received: from Debian-debbugs by debbugs.gnu.org with local (Exim 4.84_2) (envelope-from ) id 1eO10X-0002wr-T7 for bug-gnu-emacs@gnu.org; Sun, 10 Dec 2017 07:47:01 -0500 X-Loop: help-debbugs@gnu.org Resent-From: Simen =?UTF-8?Q?Heggest=C3=B8yl?= Original-Sender: "Debbugs-submit" Resent-CC: bug-gnu-emacs@gnu.org Resent-Date: Sun, 10 Dec 2017 12:47:01 +0000 Resent-Message-ID: Resent-Sender: help-debbugs@gnu.org X-GNU-PR-Message: followup 29456 X-GNU-PR-Package: emacs X-GNU-PR-Keywords: patch Original-Received: via spool by 29456-submit@debbugs.gnu.org id=B29456.151290998611267 (code B ref 29456); Sun, 10 Dec 2017 12:47:01 +0000 Original-Received: (at 29456) by debbugs.gnu.org; 10 Dec 2017 12:46:26 +0000 Original-Received: from localhost ([127.0.0.1]:54557 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1eO0zx-0002vd-Hx for submit@debbugs.gnu.org; Sun, 10 Dec 2017 07:46:26 -0500 Original-Received: from mail-lf0-f54.google.com ([209.85.215.54]:37173) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1eO0zw-0002vM-4z for 29456@debbugs.gnu.org; Sun, 10 Dec 2017 07:46:24 -0500 Original-Received: by mail-lf0-f54.google.com with SMTP id a12so16275950lfe.4 for <29456@debbugs.gnu.org>; Sun, 10 Dec 2017 04:46:24 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=date:from:subject:to:cc:message-id:in-reply-to:references :mime-version; bh=VhUF/O4BrK/Fq23NL1gKy8jlqRQTdyK8Wx/bs9SQNxo=; b=jmYyHOaiGZ+V9TVtr+2utnFVDGe8rpw2DnmJYNCIIzXDPdhx/MZxqGoiM1gfDizlxV q7bTpAS9IzSoHH8p6GNG/ce4kPpzpT/iC7Z+wgT7MqgKZyCj3E4S9+uTGAK783yWSvTQ ljnACxurP5VGbcslQ1bR6jwT0F5ToNvzbzBDgxrJUpqP5TXXF3pKSnRLE5YlsO9cuFCy uF/Eu5zRdTw1cD9iRi83WZ2/SWAljaZX4ds+BGBeBf/PdYZGjVYNAsQIArKM2w+fOXGm YYUUHjJFwm9lvlxecT5UlpSe9mKJEEy0eAaIWL0LirkXW7p0aLKYp/NnNV6V3xnPlVQR c+kQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:date:from:subject:to:cc:message-id:in-reply-to :references:mime-version; bh=VhUF/O4BrK/Fq23NL1gKy8jlqRQTdyK8Wx/bs9SQNxo=; b=o8ng4UrAC8jO+/zFaCFqJq+Gozm4FXJPZ9yUUskD05wTvlonCSOTUyuluEMcT7RPa3 8nKQ48q7RvjaXY5u4NGLZF+JbjXxY6gUfCnILx0s8QC10jhONXpXe7PvQ6iC/eNVRxx1 laYa+XvnEXJwCLs1HTBvhUgmNrn/HzCGyrrY6ArolnoYKnHkE1goypG10ydU+6AatRiw GPjAzm5xKOkF74OoGcY2uTdXwXmBsLhHRDVvhHF1DDkO0f+Ydeh9hE2kYyxepx0S1a8M 4eIXHb2vYxdNG2w9wtgU21V/WrZ7Kq2nSN42HyDmN7Arb9S4rQjaS6IqA3AgEa61Jhmk Gr/Q== X-Gm-Message-State: AJaThX4KeolJujxXAenGEKnCnzX02WoDqUD9MNbrPP5R3WhK/PbP+5sz blqcWR5C53BDkzzN4ULT2J8= X-Google-Smtp-Source: AGs4zMbx1bDrW96qcVKdSDrKNOMtoQ8RC6WX1Fh7WiL8dh6LgY4XQF625N4R/6iwPhGT8DHMm3BT/A== X-Received: by 10.25.215.167 with SMTP id q39mr15520394lfi.33.1512909978127; Sun, 10 Dec 2017 04:46:18 -0800 (PST) Original-Received: from [192.168.100.12] (cm-84.210.143.4.getinternet.no. [84.210.143.4]) by smtp.gmail.com with ESMTPSA id v17sm2287391ljv.38.2017.12.10.04.46.16 (version=TLS1_2 cipher=ECDHE-RSA-CHACHA20-POLY1305 bits=256/256); Sun, 10 Dec 2017 04:46:16 -0800 (PST) In-Reply-To: <87shcyce9x.fsf@gmail.com> X-Mailer: geary/0.12-dev X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic] X-Received-From: 208.118.235.43 X-BeenThere: bug-gnu-emacs@gnu.org List-Id: "Bug reports for GNU Emacs, the Swiss army knife of text editors" List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: bug-gnu-emacs-bounces+geb-bug-gnu-emacs=m.gmane.org@gnu.org Original-Sender: "bug-gnu-emacs" Xref: news.gmane.org gmane.emacs.bugs:140888 Archived-At: --=-08ow+NF3IG0A/dGjfhVQ Content-Type: multipart/alternative; boundary="=-l4uZOPXb7vUMy18cbZZI" --=-l4uZOPXb7vUMy18cbZZI Content-Type: text/plain; charset=us-ascii; format=flowed I've updated the patch to support switching between #RGBA/#RRGGBBAA and rgba() too, fixing a bug in `css--hex-color' along the way for handling the #RGBA case. If you agree, I think that fix should go into the emacs-26 branch as well. I also prefixed the function names with `css--' to more strongly indicate that they're intended for internal usage, and mentioned in the docstrings their incompatibility with color.el. -- Simen --=-l4uZOPXb7vUMy18cbZZI Content-Type: text/html; charset=us-ascii
I've updated the patch to support switching between #RGBA/#RRGGBBAA and
rgba() too, fixing a bug in `css--hex-color' along the way for handling
the #RGBA case. If you agree, I think that fix should go into the
emacs-26 branch as well.

I also prefixed the function names with `css--' to more strongly
indicate that they're intended for internal usage, and mentioned in the
docstrings their incompatibility with color.el.

-- Simen
--=-l4uZOPXb7vUMy18cbZZI-- --=-08ow+NF3IG0A/dGjfhVQ Content-Type: text/x-patch Content-Disposition: attachment; filename=0001-Add-command-for-cycling-between-CSS-color-formats.patch Content-Transfer-Encoding: quoted-printable >From 70d8834de32e31862dc0f14ce80f7e608e4151db Mon Sep 17 00:00:00 2001 From: =3D?UTF-8?q?Simen=3D20Heggest=3DC3=3DB8yl?=3D 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--rgb-color): Add support for extracting alpha component. (css--hex-color): Correct function when the hex string is in the #RGBA format. (css--hex-alpha, css--color-to-4-dpc, css--named-color-to-hex) (css--format-rgba-alpha, 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-color-to-4-dpc): (css-test-named-color-to-hex, css-test-format-rgba-alpha) (css-test-hex-to-rgb, css-test-rgb-to-named-color-or-hex) (css-test-cycle-color-format, css-test-hex-color) (css-test-hex-alpha): New tests for the changes mentioned above. * etc/NEWS: Mention the new command. --- etc/NEWS | 7 ++ lisp/textmodes/css-mode.el | 133 ++++++++++++++++++++++++++++++= ++-- test/lisp/textmodes/css-mode-tests.el | 77 ++++++++++++++++++++ 3 files changed, 210 insertions(+), 7 deletions(-) diff --git a/etc/NEWS b/etc/NEWS index dd7d983970..33eaf257df 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -63,6 +63,13 @@ whether '"' is also replaced in 'electric-quote-mode'. = If non-nil, =0C * Changes in Specialized Modes and Packages in Emacs 27.1 =20 +** CSS mode + +--- +*** A new command 'css-cycle-color-format' for cycling between color +formats (e.g. "black" =3D> "#000000" =3D> "rgb(0, 0, 0)") has been added, +bound to 'C-c C-f'. + ** Dired =20 +++ diff --git a/lisp/textmodes/css-mode.el b/lisp/textmodes/css-mode.el index 93ca36b08a..6bf365ecb3 100644 --- a/lisp/textmodes/css-mode.el +++ b/lisp/textmodes/css-mode.el @@ -32,12 +32,13 @@ =20 ;;; Code: =20 -(require 'eww) (require 'cl-lib) (require 'color) +(require 'eww) (require 'seq) (require 'sgml-mode) (require 'smie) +(require 'thingatpt) (eval-when-compile (require 'subr-x)) =20 (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'.") =20 @@ -936,11 +938,13 @@ css--color-skip-blanks "Skip blanks and comments." (while (forward-comment 1))) =20 -(cl-defun css--rgb-color () +(cl-defun css--rgb-color (&optional include-alpha) "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." +This recognizes CSS-color-4 extensions. +When INCLUDE-ALPHA is non-nil, the alpha component is included in +the returned hex string." (let ((result '()) (iter 0)) (while (< iter 4) @@ -952,8 +956,8 @@ css--color-skip-blanks (number (string-to-number str))) (when is-percent (setq number (* 255 (/ number 100.0)))) - ;; Don't push the alpha. - (when (< iter 3) + (if (and include-alpha (=3D iter 3)) + (push (round (* number 255)) result) (push (min (max 0 (truncate number)) 255) result)) (goto-char (match-end 0)) (css--color-skip-blanks) @@ -966,7 +970,11 @@ css--color-skip-blanks (css--color-skip-blanks))) (when (looking-at ")") (forward-char) - (apply #'format "#%02x%02x%02x" (nreverse result))))) + (apply #'format + (if (and include-alpha (=3D (length result) 4)) + "#%02x%02x%02x%02x" + "#%02x%02x%02x") + (nreverse result))))) =20 (cl-defun css--hsl-color () "Parse a CSS hsl() or hsla() color. @@ -1037,10 +1045,18 @@ css--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) + (if (> (length str) 5) (substring str 0 7) (substring str 0 4))) =20 +(defun css--hex-alpha (hex) + "Return the alpha component of CSS color HEX. +HEX can either be in the #RGBA or #RRGGBBAA format. If the color +doesn't have an alpha component, nil is returned." + (cl-case (length hex) + (5 (string (elt hex 4))) + (9 (substring hex 7 9)))) + (defun css--named-color (start-point str) "Check whether STR, seen at point, is CSS named color. Returns STR if it is a valid color. Special care is taken @@ -1383,6 +1399,109 @@ css-completion-at-point (progn (insert ": ;") (forward-char -1)))))))))) =20 +(defun css--color-to-4-dpc (hex) + "Convert the CSS color HEX to four digits per component. +CSS colors use one or two digits per component for RGB hex +values. Convert the given color to four digits per component. + +Note that this function handles CSS colors specifically, and +should not be mixed with those in color.el." + (let ((six-digits (=3D (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 CSS color at point to hex format. +Return non-nil if a conversion was made. + +Note that this function handles CSS colors specifically, and +should not be mixed with those in color.el." + (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--format-rgba-alpha (alpha) + "Return ALPHA component formatted for use in rgba()." + (if (or (=3D alpha 0) + (=3D alpha 1)) + (format "%d" alpha) + (string-remove-suffix "0" (format "%.2f" alpha)))) + +(defun css--hex-to-rgb () + "Convert CSS hex color at point to RGB format. +Return non-nil if a conversion was made. + +Note that this function handles CSS colors specifically, and +should not be mixed with those in color.el." + (save-excursion + (unless (or (eq (char-after) ?#) + (eq (char-before) ?\()) + (backward-sexp)) + (when-let* ((hex (when (looking-at css--colors-regexp) + (and (eq (elt (match-string 0) 0) ?#) + (match-string 0)))) + (rgb (css--hex-color hex))) + (seq-let (r g b) + (mapcar (lambda (x) (round (* x 255))) + (color-name-to-rgb (css--color-to-4-dpc rgb))) + (replace-match + (if-let* ((alpha (css--hex-alpha hex)) + (a (css--format-rgba-alpha + (/ (string-to-number alpha 16) + (float (expt 16 (length alpha))))))) + (format "rgba(%d, %d, %d, %s)" r g b a) + (format "rgb(%d, %d, %d)" r g b)))) + t))) + +(defun css--rgb-to-named-color-or-hex () + "Convert CSS 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. + +Note that this function handles CSS colors specifically, and +should not be mixed with those in color.el." + (save-excursion + (when-let* ((open-paren-pos (nth 1 (syntax-ppss)))) + (when (save-excursion + (goto-char open-paren-pos) + (looking-back "rgba?" (- (point) 4))) + (goto-char (nth 1 (syntax-ppss))))) + (when (eq (char-before) ?\)) + (backward-sexp)) + (skip-chars-backward "rgba") + (when (looking-at css--colors-regexp) + (let* ((start (match-end 0)) + (color (save-excursion + (goto-char start) + (css--rgb-color t)))) + (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 CSS color 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/cs= s-mode-tests.el index 47cf5f9244..f3d4243721 100644 --- a/test/lisp/textmodes/css-mode-tests.el +++ b/test/lisp/textmodes/css-mode-tests.el @@ -244,6 +244,71 @@ css-mode-tests--completions (should (member "body" completions)) (should-not (member "article" completions))))) =20 +(ert-deftest css-test-color-to-4-dpc () + (should (equal (css--color-to-4-dpc "#ffffff") + (css--color-to-4-dpc "#fff"))) + (should (equal (css--color-to-4-dpc "#aabbcc") + (css--color-to-4-dpc "#abc"))) + (should (equal (css--color-to-4-dpc "#fab") + "#ffffaaaabbbb")) + (should (equal (css--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-format-rgba-alpha () + (should (equal (css--format-rgba-alpha 0) "0")) + (should (equal (css--format-rgba-alpha 0.0) "0")) + (should (equal (css--format-rgba-alpha 1) "1")) + (should (equal (css--format-rgba-alpha 1.0) "1")) + (should (equal (css--format-rgba-alpha 0.10000) "0.1")) + (should (equal (css--format-rgba-alpha 0.100001) "0.1")) + (should (equal (css--format-rgba-alpha 0.2524334) "0.25"))) + +(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)") + ("#ffffff80" "rgba(255, 255, 255, 0.5)") + ("#fff8" "rgba(255, 255, 255, 0.5)"))) + (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") + ("rgba(18, 52, 86, 0.5)" "#12345680"))) + (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") @@ -295,6 +360,18 @@ css-mode-tests--completions (insert input ")")) (should (equal (css--hsl-color) "#ff0000"))))) =20 +(ert-deftest css-test-hex-color () + (should (equal (css--hex-color "#abc") "#abc")) + (should (equal (css--hex-color "#abcd") "#abc")) + (should (equal (css--hex-color "#aabbcc") "#aabbcc")) + (should (equal (css--hex-color "#aabbccdd") "#aabbcc"))) + +(ert-deftest css-test-hex-alpha () + (should (equal (css--hex-alpha "#abcd") "d")) + (should-not (css--hex-alpha "#abc")) + (should (equal (css--hex-alpha "#aabbccdd") "dd")) + (should-not (css--hex-alpha "#aabbcc"))) + (ert-deftest css-test-named-color () (dolist (text '("@mixin black" "@include black")) (with-temp-buffer --=20 2.15.1 = --=-08ow+NF3IG0A/dGjfhVQ--