Expand css-ts-mode and merge it into css-mode

* lisp/progmodes/css-ts-mode.el: Deleted.
* lisp/textmodes/css-mode.el (css--treesit-indent-rules)
(css--treesit-settings): New variables.
(css--treesit-imenu-1)
(css--treesit-imenu): New functions.
* lisp/textmodes/css-mode.el (css-base-mode): New mode inherited by
both css-mode and css-ts-mode.
(css-ts-mode): New mode.
(css-mode): Inherit from css-base-mode, and move some setup to
css-base-mode.
This commit is contained in:
Yuan Fu 2022-11-19 15:27:07 -08:00
parent e41af3971d
commit 655957087c
No known key found for this signature in database
GPG key ID: 56E19BC57664A442
2 changed files with 151 additions and 146 deletions

View file

@ -1,136 +0,0 @@
;;; 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

View file

@ -40,7 +40,9 @@
(require 'sgml-mode) (require 'sgml-mode)
(require 'smie) (require 'smie)
(require 'thingatpt) (require 'thingatpt)
(eval-when-compile (require 'subr-x)) (eval-when-compile (require 'subr-x)
(require 'rx))
(require 'treesit)
(defgroup css nil (defgroup css nil
"Cascading Style Sheets (CSS) editing mode." "Cascading Style Sheets (CSS) editing mode."
@ -1319,6 +1321,94 @@ for determining whether point is within a selector."
(when (smie-rule-hanging-p) (when (smie-rule-hanging-p)
css-indent-offset)))) 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 ;;; Completion
(defun css--complete-property () (defun css--complete-property ()
@ -1657,8 +1747,67 @@ rgb()/rgba()."
(replace-regexp-in-string "[\n ]+" " " s))) (replace-regexp-in-string "[\n ]+" " " s)))
res))))))) 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 ;;;###autoload
(define-derived-mode css-mode prog-mode "CSS" (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 css-base-mode "CSS"
"Major mode to edit Cascading Style Sheets (CSS). "Major mode to edit Cascading Style Sheets (CSS).
\\<css-mode-map> \\<css-mode-map>
This mode provides syntax highlighting, indentation, completion, This mode provides syntax highlighting, indentation, completion,
@ -1679,10 +1828,6 @@ be used to fill comments.
\\{css-mode-map}" \\{css-mode-map}"
(setq-local font-lock-defaults css-font-lock-defaults) (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 (setq-local syntax-propertize-function
css-syntax-propertize-function) css-syntax-propertize-function)
(setq-local fill-paragraph-function #'css-fill-paragraph) (setq-local fill-paragraph-function #'css-fill-paragraph)
@ -1691,13 +1836,9 @@ be used to fill comments.
(smie-setup css-smie-grammar #'css-smie-rules (smie-setup css-smie-grammar #'css-smie-rules
:forward-token #'css-smie--forward-token :forward-token #'css-smie--forward-token
:backward-token #'css-smie--backward-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) (setq-local font-lock-fontify-region-function #'css--fontify-region)
(add-hook 'completion-at-point-functions (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-prev-index-position-function
#'css--prev-index-position) #'css--prev-index-position)
(setq-local imenu-extract-index-name-function (setq-local imenu-extract-index-name-function