(defface css-proprietary-property '((t :inherit (css-property italic)))
"Face to use for vendor-specific properties.")
+(defun css--selector-regexp (sassy)
+ (concat
+ "\\(?:"
+ (if (not sassy)
+ "[-_%*#.>[:alnum:]]+"
+ ;; Same as for non-sassy except we do want to allow { and }
+ ;; chars in selectors in the case of #{$foo}
+ ;; variable interpolation!
+ (concat "\\(?:[-_%*#.>[:alnum:]]*" scss--hash-re
+ "\\|[-_%*#.>[:alnum:]]+\\)"))
+ ;; Even though pseudo-elements should be prefixed by ::, a
+ ;; single colon is accepted for backward compatibility.
+ "\\(?:\\(:" (regexp-opt (append css-pseudo-class-ids
+ css-pseudo-element-ids)
+ t)
+ "\\|::" (regexp-opt css-pseudo-element-ids t) "\\)\\)?"
+ ;; Braces after selectors.
+ "\\(?:\\[[^]\n]+\\]\\)?"
+ ;; Parentheses after selectors.
+ "\\(?:([^)]+)\\)?"
+ ;; Main bit over. But perhaps just [target]?
+ "\\|\\[[^]\n]+\\]"
+ ;; :root, ::marker and the like.
+ "\\|::?[[:alnum:]]+\\(?:([^)]+)\\)?"
+ "\\)"))
+
(defun css--font-lock-keywords (&optional sassy)
`((,(concat "!\\s-*" (regexp-opt css--bang-ids))
(0 font-lock-builtin-face))
;; selector between [...] should simply not be highlighted.
(,(concat
"^[ \t]*\\("
- (if (not sassy)
- ;; We don't allow / as first char, so as not to
- ;; take a comment as the beginning of a selector.
- "[^@/:{}() \t\n][^:{}()]*"
- ;; Same as for non-sassy except we do want to allow { and }
- ;; chars in selectors in the case of #{$foo}
- ;; variable interpolation!
- (concat "\\(?:" scss--hash-re
- "\\|[^@/:{}() \t\n#]\\)"
- "[^:{}()#]*\\(?:" scss--hash-re "[^:{}()#]*\\)*"))
- ;; Even though pseudo-elements should be prefixed by ::, a
- ;; single colon is accepted for backward compatibility.
- "\\(?:\\(:" (regexp-opt (append css-pseudo-class-ids
- css-pseudo-element-ids)
- t)
- "\\|::" (regexp-opt css-pseudo-element-ids t) "\\)"
- "\\(?:([^)]+)\\)?"
- (if (not sassy)
- "[^:{}()\n]*"
- (concat "[^:{}()\n#]*\\(?:" scss--hash-re "[^:{}()\n#]*\\)*"))
+ ;; We have at least one selector.
+ (css--selector-regexp sassy)
+ ;; And then possibly more.
+ "\\(?:"
+ ;; Separators between selectors.
+ "[ \n\t,+~>]+"
+ (css--selector-regexp sassy)
"\\)*"
- "\\)\\(?:\n[ \t]*\\)*{")
+ ;; And then a brace.
+ "\\)[ \n\t]*{")
(1 'css-selector keep))
;; In the above rule, we allow the open-brace to be on some subsequent
;; line. This will only work if we properly mark the intervening text
--- /dev/null
+#firstname
+*
+p
+p.intro
+div, p
+div p
+div > p
+div + p
+p ~ ul
+[target]
+[target=_blank]
+[title~=flower]
+[lang|=en]
+a[href^="https"]
+a[href$=".pdf"]
+a[href*="w3schools"]
+a:active
+p::after
+p::before
+input:checked
+input:default
+input:disabled
+p:empty
+input:enabled
+p:first-child
+p::first-letter
+p::first-line
+p:first-of-type
+input:focus
+:fullscreen
+a:hover
+input:in-range
+input:indeterminate
+input:invalid
+p:lang(it)
+p:last-child
+p:last-of-type
+a:link
+::marker
+:not(p)
+p:nth-child(2)
+p:nth-last-child(2)
+p:nth-last-of-type(2)
+p:nth-of-type(2)
+p:only-of-type
+p:only-child
+input:optional
+input:out-of-range
+input:read-only
+input:read-write
+input:required
+:root
+::selection
+#news:target
+input:valid
+a:visited
--- /dev/null
+p.#{$name} var
+p.#{$name}:active var
+p.#{$name}::after var
+f.#{$bar}::after p::after
+p.#{$name} f.#{$bar} k.var #{$bar} #{$bar}
+p.#{$name}
(indent-region (point-min) (point-max))
(should (equal (buffer-string) orig)))))
+(ert-deftest css-mode-test-selectors ()
+ (let ((selectors
+ (with-temp-buffer
+ (insert-file-contents (ert-resource-file "css-selectors.txt"))
+ (string-lines (buffer-string)))))
+ (with-suppressed-warnings ((interactive font-lock-debug-fontif))
+ (dolist (selector selectors)
+ (with-temp-buffer
+ (css-mode)
+ (insert selector " {\n}\n")
+ (font-lock-debug-fontify)
+ (goto-char (point-min))
+ (unless (eq (get-text-property (point) 'face)
+ 'css-selector)
+ (should-not (format "Didn't recognize %s as a selector"
+ (buffer-substring-no-properties
+ (point) (line-end-position)))))))
+ ;; Test many selectors.
+ (dolist (selector selectors)
+ (with-temp-buffer
+ (css-mode)
+ (insert selector " ")
+ (dotimes (_ (random 5))
+ (insert (seq-random-elt '(" , " " > " " + "))
+ (seq-random-elt selectors)))
+ (insert "{\n}\n")
+ (font-lock-debug-fontify)
+ (goto-char (point-min))
+ (unless (eq (get-text-property (point) 'face)
+ 'css-selector)
+ (should-not (format "Didn't recognize %s as a selector"
+ (buffer-substring-no-properties
+ (point) (line-end-position)))))))
+ ;; Test wrong separators.
+ (dolist (selector selectors)
+ (with-temp-buffer
+ (css-mode)
+ (insert selector " ")
+ (dotimes (_ (1+ (random 5)))
+ (insert (seq-random-elt '("=" " @ "))
+ (seq-random-elt selectors)))
+ (insert "{\n}\n")
+ (font-lock-debug-fontify)
+ (goto-char (point-min))
+ (when (eq (get-text-property (point) 'face)
+ 'css-selector)
+ (should-not (format "Recognized %s as a selector"
+ (buffer-substring-no-properties
+ (point) (line-end-position))))))))))
+
+(ert-deftest scss-mode-test-selectors ()
+ (let ((selectors
+ (with-temp-buffer
+ (insert-file-contents (ert-resource-file "scss-selectors.txt"))
+ (string-lines (buffer-string)))))
+ (with-suppressed-warnings ((interactive font-lock-debug-fontif))
+ (dolist (selector selectors)
+ (with-temp-buffer
+ (scss-mode)
+ (insert selector " {\n}\n")
+ (font-lock-debug-fontify)
+ (goto-char (point-min))
+ (unless (eq (get-text-property (point) 'face)
+ 'css-selector)
+ (should-not (format "Didn't recognize %s as a selector"
+ (buffer-substring-no-properties
+ (point) (line-end-position))))))))))
+
+
(provide 'css-mode-tests)
;;; css-mode-tests.el ends here