>From 54f258b39822d7042c240fe6bbde63ccfb49e59d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simen=20Heggest=C3=B8yl?= 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