diff options
| author | Simen Heggestøyl | 2018-05-29 19:14:34 +0200 |
|---|---|---|
| committer | Simen Heggestøyl | 2018-05-29 21:24:26 +0200 |
| commit | 58d0642e1ca006fa550bff50fd328bc166c572da (patch) | |
| tree | a963419d50eac188e629bba7cbb68fcd833a2290 | |
| parent | 5be83e343f9f0f3487793b54ff95bc89ee6b824a (diff) | |
| download | emacs-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/NEWS | 3 | ||||
| -rw-r--r-- | lisp/textmodes/css-mode.el | 66 | ||||
| -rw-r--r-- | test/lisp/textmodes/css-mode-tests.el | 15 |
3 files changed, 76 insertions, 8 deletions
| @@ -263,6 +263,9 @@ Can be controlled via the new variable 'footnote-align-to-fn-text'. | |||
| 263 | formats (e.g. "black" => "#000000" => "rgb(0, 0, 0)") has been added, | 263 | formats (e.g. "black" => "#000000" => "rgb(0, 0, 0)") has been added, |
| 264 | bound to 'C-c C-f'. | 264 | bound 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") |