Commit 58d0642e authored by Simen Heggestøyl's avatar Simen Heggestøyl

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
(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

* etc/NEWS: Add news entry.
parent 5be83e34
......@@ -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
......@@ -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()."
(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)
((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.
(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)
(goto-char (point-min)))
(forward-comment (point-max))
(save-excursion (re-search-forward "[^{;]*"))))
(defun css--extract-index-name ()
(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)
(goto-char (point-min)))
(forward-comment (point-max))
(when (save-excursion
(re-search-forward "[^{;]*"))
(push (match-string-no-properties 0) res)))
(lambda (s)
(replace-regexp-in-string "[\n ]+" " " s)))
(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
(setq-local imenu-extract-index-name-function
(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."
(let ((max (max (point-min) (- (point) 1600)))) ; approx 20 lines back
(when (search-backward "{" max t)
(skip-chars-backward " \t\r\n")
(if (looking-at "^[ \t]*\\([^{\r\n]*[^ {\t\r\n]\\)")
(match-string-no-properties 1))))))
(when (css--prev-index-position)
;;; SCSS mode
......@@ -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 ()
......@@ -324,6 +324,19 @@
(should (equal (buffer-string) "black"))))
(ert-deftest css-test-join-nested-selectors ()
(should (equal (css--join-nested-selectors '("div" "&:hover"))
(equal (css--join-nested-selectors '("a" "&::before, &::after"))
"a::before, a::after"))
(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")
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment