From 655957087c8654577e7c59004f16be7abcc2c46c Mon Sep 17 00:00:00 2001 From: Yuan Fu Date: Sat, 19 Nov 2022 15:27:07 -0800 Subject: [PATCH] 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. --- lisp/progmodes/css-ts-mode.el | 136 ---------------------------- lisp/textmodes/css-mode.el | 161 +++++++++++++++++++++++++++++++--- 2 files changed, 151 insertions(+), 146 deletions(-) delete mode 100644 lisp/progmodes/css-ts-mode.el diff --git a/lisp/progmodes/css-ts-mode.el b/lisp/progmodes/css-ts-mode.el deleted file mode 100644 index d0f104eac34..00000000000 --- a/lisp/progmodes/css-ts-mode.el +++ /dev/null @@ -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 -;; Maintainer : Theodor Thornhill -;; 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 . - - -;;; 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 diff --git a/lisp/textmodes/css-mode.el b/lisp/textmodes/css-mode.el index d2a35bd550f..34380c05a00 100644 --- a/lisp/textmodes/css-mode.el +++ b/lisp/textmodes/css-mode.el @@ -40,7 +40,9 @@ (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." @@ -1319,6 +1321,94 @@ for determining whether point is within a selector." (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 () @@ -1657,8 +1747,67 @@ rgb()/rgba()." (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). +\\ + +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). \\ This mode provides syntax highlighting, indentation, completion, @@ -1679,10 +1828,6 @@ be used to fill comments. \\{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) @@ -1691,13 +1836,9 @@ be used to fill comments. (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 -- 2.39.5