]> git.eshelyaron.com Git - emacs.git/commitdiff
Add Imenu support to CSS mode and its derivatives
authorSimen Heggestøyl <simenheg@gmail.com>
Tue, 29 May 2018 17:14:34 +0000 (19:14 +0200)
committerSimen Heggestøyl <simenheg@gmail.com>
Tue, 29 May 2018 19:24:26 +0000 (21:24 +0200)
* 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.

etc/NEWS
lisp/textmodes/css-mode.el
test/lisp/textmodes/css-mode-tests.el

index ea4a657cba9e851866115719d0561845323309f6..bd25f43ad06a8541734b1e2f71c4f73441320c3a 100644 (file)
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -263,6 +263,9 @@ Can be controlled via the new variable 'footnote-align-to-fn-text'.
 formats (e.g. "black" => "#000000" => "rgb(0, 0, 0)") has been added,
 bound to 'C-c C-f'.
 
+---
+*** CSS mode, SCSS mode, and Less CSS mode now have support for Imenu.
+
 ** SGML mode
 
 ---
index 727bc18ebb8cb2ac6568ddee6bc78a88200ac9ae..6d06dddadca576a1b57a237f4d089b40b6deff66 100644 (file)
@@ -35,6 +35,7 @@
 (require 'cl-lib)
 (require 'color)
 (require 'eww)
+(require 'imenu)
 (require 'seq)
 (require 'sgml-mode)
 (require 'smie)
@@ -1516,6 +1517,55 @@ rgb()/rgba()."
       (css--rgb-to-named-color-or-hex)
       (message "It doesn't look like a color at point")))
 
+(defun css--join-nested-selectors (selectors)
+  "Join a list of nested CSS selectors."
+  (let ((processed '())
+        (prev nil))
+    (dolist (sel selectors)
+      (cond
+       ((seq-contains sel ?&)
+        (setq sel (replace-regexp-in-string "&" prev sel))
+        (pop processed))
+       ;; Unless this is the first selector, separate this one and the
+       ;; previous one by a space.
+       (processed
+        (push " " processed)))
+      (push sel processed)
+      (setq prev sel))
+    (apply #'concat (nreverse processed))))
+
+(defun css--prev-index-position ()
+  (when (nth 7 (syntax-ppss))
+    (goto-char (comment-beginning)))
+  (forward-comment (- (point)))
+  (when (search-backward "{" (point-min) t)
+    (if (re-search-backward "}\\|;\\|{" (point-min) t)
+        (forward-char)
+      (goto-char (point-min)))
+    (forward-comment (point-max))
+    (save-excursion (re-search-forward "[^{;]*"))))
+
+(defun css--extract-index-name ()
+  (save-excursion
+    (let ((res (list (match-string-no-properties 0))))
+      (condition-case nil
+          (while t
+            (goto-char (nth 1 (syntax-ppss)))
+            (if (re-search-backward "}\\|;\\|{" (point-min) t)
+                (forward-char)
+              (goto-char (point-min)))
+            (forward-comment (point-max))
+            (when (save-excursion
+                    (re-search-forward "[^{;]*"))
+              (push (match-string-no-properties 0) res)))
+        (error
+         (css--join-nested-selectors
+          (mapcar
+           (lambda (s)
+             (string-trim
+              (replace-regexp-in-string "[\n ]+" " " s)))
+           res)))))))
+
 ;;;###autoload
 (define-derived-mode css-mode prog-mode "CSS"
   "Major mode to edit Cascading Style Sheets (CSS).
@@ -1551,7 +1601,13 @@ Network (MDN).
               (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))
+            #'css-completion-at-point nil 'local)
+  ;; The default "." creates ambiguity with class selectors.
+  (setq-local imenu-space-replacement " ")
+  (setq-local imenu-prev-index-position-function
+              #'css--prev-index-position)
+  (setq-local imenu-extract-index-name-function
+              #'css--extract-index-name))
 
 (defvar comment-continue)
 
@@ -1648,12 +1704,8 @@ Network (MDN).
 (defun css-current-defun-name ()
   "Return the name of the CSS section at point, or nil."
   (save-excursion
-    (let ((max (max (point-min) (- (point) 1600))))  ; approx 20 lines back
-      (when (search-backward "{" max t)
-       (skip-chars-backward " \t\r\n")
-       (beginning-of-line)
-       (if (looking-at "^[ \t]*\\([^{\r\n]*[^ {\t\r\n]\\)")
-           (match-string-no-properties 1))))))
+    (when (css--prev-index-position)
+      (css--extract-index-name))))
 
 ;;; SCSS mode
 
index b0283bfa455d43b3a8725e824c861c03a2210ce7..bfae1bf2f759546bdc10bcc6dea3aacc9bee87cd 100644 (file)
@@ -85,7 +85,7 @@
     (insert "body { top: 0; }")
     (goto-char 7)
     (should (equal (css-current-defun-name) "body"))
-    (goto-char 18)
+    (goto-char 15)
     (should (equal (css-current-defun-name) "body"))))
 
 (ert-deftest css-test-current-defun-name-nested ()
     (css-cycle-color-format)
     (should (equal (buffer-string) "black"))))
 
+(ert-deftest css-test-join-nested-selectors ()
+  (should (equal (css--join-nested-selectors '("div" "&:hover"))
+                 "div:hover"))
+  (should
+   (equal (css--join-nested-selectors '("a" "&::before, &::after"))
+          "a::before, a::after"))
+  (should
+   (equal (css--join-nested-selectors
+           '("article" "& > .front-page" "& h1, & h2"))
+          "article > .front-page h1, article > .front-page h2"))
+  (should (equal (css--join-nested-selectors '(".link" "& + &"))
+                 ".link + .link")))
+
 (ert-deftest css-mdn-symbol-guessing ()
   (dolist (item '(("@med" "ia" "@media")
                   ("@keyframes " "{" "@keyframes")