+++ /dev/null
-;;; css-ts-mode.el --- tree-sitter support for CSS -*- lexical-binding: t; -*-
-
-;; Copyright (C) 2022 Free Software Foundation, Inc.
-
-;; Author : Theodor Thornhill <theo@thornhill.no>
-;; Maintainer : Theodor Thornhill <theo@thornhill.no>
-;; Created : November 2022
-;; Keywords : css languages tree-sitter
-
-;; This file is part of GNU Emacs.
-
-;; This program is free software; you can redistribute it and/or modify
-;; it under the terms of the GNU General Public License as published by
-;; the Free Software Foundation, either version 3 of the License, or
-;; (at your option) any later version.
-
-;; This program is distributed in the hope that it will be useful,
-;; but WITHOUT ANY WARRANTY; without even the implied warranty of
-;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-;; GNU General Public License for more details.
-
-;; You should have received a copy of the GNU General Public License
-;; along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-
-;;; Commentary:
-;;
-
-;;; Code:
-
-(require 'treesit)
-(require 'rx)
-(require 'css-mode)
-
-(defcustom css-ts-mode-indent-offset 2
- "Number of spaces for each indentation step in `ts-mode'."
- :version "29.1"
- :type 'integer
- :safe 'integerp
- :group 'css)
-
-(defvar css-ts-mode--indent-rules
- `((css
- ((node-is "}") parent-bol 0)
- ((node-is ")") parent-bol 0)
- ((node-is "]") parent-bol 0)
-
- ((parent-is "block") parent-bol css-ts-mode-indent-offset)
- ((parent-is "arguments") parent-bol css-ts-mode-indent-offset)
- ((parent-is "declaration") parent-bol css-ts-mode-indent-offset))))
-
-(defvar css-ts-mode--settings
- (treesit-font-lock-rules
- :language 'css
- :feature 'basic
- :override t
- `((unit) @font-lock-constant-face
- (integer_value) @font-lock-builtin-face
- (float_value) @font-lock-builtin-face
- (plain_value) @font-lock-variable-name-face
- (comment) @font-lock-comment-face
- (class_selector) @css-selector
- (child_selector) @css-selector
- (id_selector) @css-selector
- (tag_name) @css-selector
- (property_name) @css-property
- (class_name) @css-selector
- (function_name) @font-lock-function-name-face)))
-
-(defun css-ts-mode--imenu-1 (node)
- "Helper for `css-ts-mode--imenu'.
-Find string representation for NODE and set marker, then recurse
-the subtrees."
- (let* ((ts-node (car node))
- (subtrees (mapcan #'css-ts-mode--imenu-1 (cdr node)))
- (name (when ts-node
- (if (equal (treesit-node-type ts-node) "tag_name")
- (treesit-node-text ts-node)
- (treesit-node-text (treesit-node-child ts-node 1) t))))
- (marker (when ts-node
- (set-marker (make-marker)
- (treesit-node-start ts-node)))))
- (cond
- ((null ts-node) subtrees)
- (subtrees
- `((,name ,(cons name marker) ,@subtrees)))
- (t
- `((,name . ,marker))))))
-
-(defun css-ts-mode--imenu ()
- "Return Imenu alist for the current buffer."
- (let* ((node (treesit-buffer-root-node))
- (tree (treesit-induce-sparse-tree
- node (rx (or "class_selector"
- "id_selector"
- "tag_name")))))
- (css-ts-mode--imenu-1 tree)))
-
-(define-derived-mode css-ts-mode prog-mode "CSS"
- "Major mode for editing CSS."
- :group 'css
- :syntax-table css-mode-syntax-table
-
- (unless (treesit-ready-p nil 'css)
- (error "Tree-sitter for CSS isn't available"))
-
- (treesit-parser-create 'css)
-
- ;; Comments
- (setq-local comment-start "/*")
- (setq-local comment-start-skip "/\\*+[ \t]*")
- (setq-local comment-end "*/")
- (setq-local comment-end-skip "[ \t]*\\*+/")
-
- ;; Indent.
- (setq-local treesit-simple-indent-rules css-ts-mode--indent-rules)
-
- ;; Electric
- (setq-local electric-indent-chars
- (append "{}():;," electric-indent-chars))
-
- ;; Navigation.
- (setq-local treesit-defun-type-regexp "rule_set")
- ;; Font-lock.
- (setq-local treesit-font-lock-settings css-ts-mode--settings)
- (setq treesit-font-lock-feature-list '((basic) () ()))
-
- ;; Imenu.
- (setq-local imenu-create-index-function #'css-ts-mode--imenu)
- (setq-local which-func-functions nil) ;; Piggyback on imenu
-
- (treesit-major-mode-setup))
-
-(provide 'css-ts-mode)
-
-;;; css-ts-mode.el ends here
(require 'sgml-mode)
(require 'smie)
(require 'thingatpt)
-(eval-when-compile (require 'subr-x))
+(eval-when-compile (require 'subr-x)
+ (require 'rx))
+(require 'treesit)
(defgroup css nil
"Cascading Style Sheets (CSS) editing mode."
(when (smie-rule-hanging-p)
css-indent-offset))))
+;;; Tree-sitter
+
+(defvar css--treesit-indent-rules
+ '((css
+ ((node-is "}") parent-bol 0)
+ ((node-is ")") parent-bol 0)
+ ((node-is "]") parent-bol 0)
+
+ ((parent-is "block") parent-bol css-indent-offset)
+ ((parent-is "arguments") parent-bol css-indent-offset)
+ ((match nil "declaration" nil 0 3) parent-bol css-indent-offset)
+ ((match nil "declaration" nil 3) (nth-sibling 2) 0)))
+ "Tree-sitter indentation rules for `css-ts-mode'.")
+
+(defvar css--treesit-settings
+ (treesit-font-lock-rules
+ :feature 'comment
+ :language 'css
+ '((comment) @font-lock-comment-face)
+
+ :feature 'string
+ :language 'css
+ '((string_value) @font-lock-string-face)
+
+ :feature 'variable
+ :language 'css
+ '((plain_value) @font-lock-variable-name-face)
+
+ :feature 'selector
+ :language 'css
+ '((class_selector) @css-selector
+ (child_selector) @css-selector
+ (id_selector) @css-selector
+ (tag_name) @css-selector
+ (class_name) @css-selector)
+
+ :feature 'property
+ :language 'css
+ `((property_name) @css-property)
+
+ :feature 'function
+ :language 'css
+ '((function_name) @font-lock-function-name-face)
+
+ :feature 'constant
+ :language 'css
+ '((integer_value) @font-lock-number-face
+ (float_value) @font-lock-number-face
+ (unit) @font-lock-constant-face)
+
+ :feature 'error
+ :language 'css
+ '((ERROR) @error))
+ "Tree-sitter font-lock settings for `css-ts-mode'.")
+
+(defun css--treesit-imenu-1 (node)
+ "Helper for `css--treesit-imenu'.
+Find string representation for NODE and set marker, then recurse
+the subtrees."
+ (let* ((ts-node (car node))
+ (subtrees (mapcan #'css--treesit-imenu-1 (cdr node)))
+ (name (when ts-node
+ (pcase (treesit-node-type ts-node)
+ ("rule_set" (treesit-node-text
+ (treesit-node-child ts-node 0) t))
+ ("media_statement"
+ (let ((block (treesit-node-child ts-node -1)))
+ (string-trim
+ (buffer-substring-no-properties
+ (treesit-node-start ts-node)
+ (treesit-node-start block))))))))
+ (marker (when ts-node
+ (set-marker (make-marker)
+ (treesit-node-start ts-node)))))
+ (cond
+ ((or (null ts-node) (null name)) subtrees)
+ (subtrees
+ `((,name ,(cons name marker) ,@subtrees)))
+ (t
+ `((,name . ,marker))))))
+
+(defun css--treesit-imenu ()
+ "Return Imenu alist for the current buffer."
+ (let* ((node (treesit-buffer-root-node))
+ (tree (treesit-induce-sparse-tree
+ node (rx (or "rule_set" "media_statement")))))
+ (css--treesit-imenu-1 tree)))
+
;;; Completion
(defun css--complete-property ()
(replace-regexp-in-string "[\n ]+" " " s)))
res)))))))
+(define-derived-mode css-base-mode prog-mode "CSS"
+ "Generic mode to edit Cascading Style Sheets (CSS).
+
+This is a generic major mode intended to be inherited by a
+concrete implementation. Currently there two concrete
+implementations: `css-mode' and `css-ts-mode'."
+ (setq-local comment-start "/*")
+ (setq-local comment-start-skip "/\\*+[ \t]*")
+ (setq-local comment-end "*/")
+ (setq-local comment-end-skip "[ \t]*\\*+/")
+ (setq-local electric-indent-chars
+ (append css-electric-keys electric-indent-chars))
+ ;; The default "." creates ambiguity with class selectors.
+ (setq-local imenu-space-replacement " "))
+
+;;;###autoload
+(define-derived-mode css-ts-mode css-base-mode "CSS"
+ "Major mode to edit Cascading Style Sheets (CSS).
+\\<css-ts-mode-map>
+
+This mode provides syntax highlighting, indentation, completion,
+and documentation lookup for CSS, based on the tree-sitter
+library.
+
+Use `\\[completion-at-point]' to complete CSS properties,
+property values, pseudo-elements, pseudo-classes, at-rules,
+bang-rules, and HTML tags, classes and IDs. Completion
+candidates for HTML class names and IDs are found by looking
+through open HTML mode buffers.
+
+Use `\\[info-lookup-symbol]' to look up documentation of CSS
+properties, at-rules, pseudo-classes, and pseudo-elements on the
+Mozilla Developer Network (MDN).
+
+Use `\\[fill-paragraph]' to reformat CSS declaration blocks. It
+can also be used to fill comments.
+
+\\{css-mode-map}"
+ (when (treesit-ready-p 'css-mode 'css)
+ ;; Borrowed from `css-native-mode'.
+ (add-hook 'completion-at-point-functions
+ #'css-completion-at-point nil 'local)
+ (setq-local fill-paragraph-function #'css-fill-paragraph)
+ (setq-local adaptive-fill-function #'css-adaptive-fill)
+ (setq-local add-log-current-defun-function #'css-current-defun-name)
+
+ ;; Tree-sitter specific setup.
+ (treesit-parser-create 'css)
+ (setq-local treesit-simple-indent-rules css--treesit-indent-rules)
+ (setq-local treesit-defun-type-regexp "rule_set")
+ (setq-local treesit-font-lock-settings css--treesit-settings)
+ (setq-local treesit-font-lock-feature-list
+ '((selector comment)
+ (property constant string)
+ (error variable function)))
+ (setq-local imenu-create-index-function #'css--treesit-imenu)
+ (setq-local which-func-functions nil)
+ (treesit-major-mode-setup)))
+
;;;###autoload
-(define-derived-mode css-mode prog-mode "CSS"
+(define-derived-mode css-mode css-base-mode "CSS"
"Major mode to edit Cascading Style Sheets (CSS).
\\<css-mode-map>
This mode provides syntax highlighting, indentation, completion,
\\{css-mode-map}"
(setq-local font-lock-defaults css-font-lock-defaults)
- (setq-local comment-start "/*")
- (setq-local comment-start-skip "/\\*+[ \t]*")
- (setq-local comment-end "*/")
- (setq-local comment-end-skip "[ \t]*\\*+/")
(setq-local syntax-propertize-function
css-syntax-propertize-function)
(setq-local fill-paragraph-function #'css-fill-paragraph)
(smie-setup css-smie-grammar #'css-smie-rules
:forward-token #'css-smie--forward-token
:backward-token #'css-smie--backward-token)
- (setq-local electric-indent-chars
- (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)
- ;; 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