aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSimen Heggestøyl2018-05-29 19:14:34 +0200
committerSimen Heggestøyl2018-05-29 21:24:26 +0200
commit58d0642e1ca006fa550bff50fd328bc166c572da (patch)
treea963419d50eac188e629bba7cbb68fcd833a2290
parent5be83e343f9f0f3487793b54ff95bc89ee6b824a (diff)
downloademacs-58d0642e1ca006fa550bff50fd328bc166c572da.tar.gz
emacs-58d0642e1ca006fa550bff50fd328bc166c572da.zip
Add Imenu support to CSS mode and its derivatives
* lisp/textmodes/css-mode.el (css--join-nested-selectors) (css--prev-index-position, css--extract-index-name): New helper functions for supporting Imenu. (css-mode): Set `imenu-space-replacement', `imenu-prev-index-position-function', and `imenu-extract-index-name-function'. (css-current-defun-name): Reuse `css--prev-index-position' and `css--extract-index-name' to support nested selectors. * test/lisp/textmodes/css-mode-tests.el (css-test-current-defun-name): Fix character index. (css-test-join-nested-selectors): New tests for `css--join-nested-selectors'. * etc/NEWS: Add news entry.
-rw-r--r--etc/NEWS3
-rw-r--r--lisp/textmodes/css-mode.el66
-rw-r--r--test/lisp/textmodes/css-mode-tests.el15
3 files changed, 76 insertions, 8 deletions
diff --git a/etc/NEWS b/etc/NEWS
index ea4a657cba9..bd25f43ad06 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -263,6 +263,9 @@ Can be controlled via the new variable 'footnote-align-to-fn-text'.
263formats (e.g. "black" => "#000000" => "rgb(0, 0, 0)") has been added, 263formats (e.g. "black" => "#000000" => "rgb(0, 0, 0)") has been added,
264bound to 'C-c C-f'. 264bound to 'C-c C-f'.
265 265
266---
267*** CSS mode, SCSS mode, and Less CSS mode now have support for Imenu.
268
266** SGML mode 269** SGML mode
267 270
268--- 271---
diff --git a/lisp/textmodes/css-mode.el b/lisp/textmodes/css-mode.el
index 727bc18ebb8..6d06dddadca 100644
--- a/lisp/textmodes/css-mode.el
+++ b/lisp/textmodes/css-mode.el
@@ -35,6 +35,7 @@
35(require 'cl-lib) 35(require 'cl-lib)
36(require 'color) 36(require 'color)
37(require 'eww) 37(require 'eww)
38(require 'imenu)
38(require 'seq) 39(require 'seq)
39(require 'sgml-mode) 40(require 'sgml-mode)
40(require 'smie) 41(require 'smie)
@@ -1516,6 +1517,55 @@ rgb()/rgba()."
1516 (css--rgb-to-named-color-or-hex) 1517 (css--rgb-to-named-color-or-hex)
1517 (message "It doesn't look like a color at point"))) 1518 (message "It doesn't look like a color at point")))
1518 1519
1520(defun css--join-nested-selectors (selectors)
1521 "Join a list of nested CSS selectors."
1522 (let ((processed '())
1523 (prev nil))
1524 (dolist (sel selectors)
1525 (cond
1526 ((seq-contains sel ?&)
1527 (setq sel (replace-regexp-in-string "&" prev sel))
1528 (pop processed))
1529 ;; Unless this is the first selector, separate this one and the
1530 ;; previous one by a space.
1531 (processed
1532 (push " " processed)))
1533 (push sel processed)
1534 (setq prev sel))
1535 (apply #'concat (nreverse processed))))
1536
1537(defun css--prev-index-position ()
1538 (when (nth 7 (syntax-ppss))
1539 (goto-char (comment-beginning)))
1540 (forward-comment (- (point)))
1541 (when (search-backward "{" (point-min) t)
1542 (if (re-search-backward "}\\|;\\|{" (point-min) t)
1543 (forward-char)
1544 (goto-char (point-min)))
1545 (forward-comment (point-max))
1546 (save-excursion (re-search-forward "[^{;]*"))))
1547
1548(defun css--extract-index-name ()
1549 (save-excursion
1550 (let ((res (list (match-string-no-properties 0))))
1551 (condition-case nil
1552 (while t
1553 (goto-char (nth 1 (syntax-ppss)))
1554 (if (re-search-backward "}\\|;\\|{" (point-min) t)
1555 (forward-char)
1556 (goto-char (point-min)))
1557 (forward-comment (point-max))
1558 (when (save-excursion
1559 (re-search-forward "[^{;]*"))
1560 (push (match-string-no-properties 0) res)))
1561 (error
1562 (css--join-nested-selectors
1563 (mapcar
1564 (lambda (s)
1565 (string-trim
1566 (replace-regexp-in-string "[\n ]+" " " s)))
1567 res)))))))
1568
1519;;;###autoload 1569;;;###autoload
1520(define-derived-mode css-mode prog-mode "CSS" 1570(define-derived-mode css-mode prog-mode "CSS"
1521 "Major mode to edit Cascading Style Sheets (CSS). 1571 "Major mode to edit Cascading Style Sheets (CSS).
@@ -1551,7 +1601,13 @@ Network (MDN).
1551 (append css-electric-keys electric-indent-chars)) 1601 (append css-electric-keys electric-indent-chars))
1552 (setq-local font-lock-fontify-region-function #'css--fontify-region) 1602 (setq-local font-lock-fontify-region-function #'css--fontify-region)
1553 (add-hook 'completion-at-point-functions 1603 (add-hook 'completion-at-point-functions
1554 #'css-completion-at-point nil 'local)) 1604 #'css-completion-at-point nil 'local)
1605 ;; The default "." creates ambiguity with class selectors.
1606 (setq-local imenu-space-replacement " ")
1607 (setq-local imenu-prev-index-position-function
1608 #'css--prev-index-position)
1609 (setq-local imenu-extract-index-name-function
1610 #'css--extract-index-name))
1555 1611
1556(defvar comment-continue) 1612(defvar comment-continue)
1557 1613
@@ -1648,12 +1704,8 @@ Network (MDN).
1648(defun css-current-defun-name () 1704(defun css-current-defun-name ()
1649 "Return the name of the CSS section at point, or nil." 1705 "Return the name of the CSS section at point, or nil."
1650 (save-excursion 1706 (save-excursion
1651 (let ((max (max (point-min) (- (point) 1600)))) ; approx 20 lines back 1707 (when (css--prev-index-position)
1652 (when (search-backward "{" max t) 1708 (css--extract-index-name))))
1653 (skip-chars-backward " \t\r\n")
1654 (beginning-of-line)
1655 (if (looking-at "^[ \t]*\\([^{\r\n]*[^ {\t\r\n]\\)")
1656 (match-string-no-properties 1))))))
1657 1709
1658;;; SCSS mode 1710;;; SCSS mode
1659 1711
diff --git a/test/lisp/textmodes/css-mode-tests.el b/test/lisp/textmodes/css-mode-tests.el
index b0283bfa455..bfae1bf2f75 100644
--- a/test/lisp/textmodes/css-mode-tests.el
+++ b/test/lisp/textmodes/css-mode-tests.el
@@ -85,7 +85,7 @@
85 (insert "body { top: 0; }") 85 (insert "body { top: 0; }")
86 (goto-char 7) 86 (goto-char 7)
87 (should (equal (css-current-defun-name) "body")) 87 (should (equal (css-current-defun-name) "body"))
88 (goto-char 18) 88 (goto-char 15)
89 (should (equal (css-current-defun-name) "body")))) 89 (should (equal (css-current-defun-name) "body"))))
90 90
91(ert-deftest css-test-current-defun-name-nested () 91(ert-deftest css-test-current-defun-name-nested ()
@@ -324,6 +324,19 @@
324 (css-cycle-color-format) 324 (css-cycle-color-format)
325 (should (equal (buffer-string) "black")))) 325 (should (equal (buffer-string) "black"))))
326 326
327(ert-deftest css-test-join-nested-selectors ()
328 (should (equal (css--join-nested-selectors '("div" "&:hover"))
329 "div:hover"))
330 (should
331 (equal (css--join-nested-selectors '("a" "&::before, &::after"))
332 "a::before, a::after"))
333 (should
334 (equal (css--join-nested-selectors
335 '("article" "& > .front-page" "& h1, & h2"))
336 "article > .front-page h1, article > .front-page h2"))
337 (should (equal (css--join-nested-selectors '(".link" "& + &"))
338 ".link + .link")))
339
327(ert-deftest css-mdn-symbol-guessing () 340(ert-deftest css-mdn-symbol-guessing ()
328 (dolist (item '(("@med" "ia" "@media") 341 (dolist (item '(("@med" "ia" "@media")
329 ("@keyframes " "{" "@keyframes") 342 ("@keyframes " "{" "@keyframes")