]> git.eshelyaron.com Git - emacs.git/commitdiff
Add cmake-ts-mode
authorRandy Taylor <dev@rjt.dev>
Sun, 11 Dec 2022 02:40:25 +0000 (21:40 -0500)
committerYuan Fu <casouri@gmail.com>
Mon, 12 Dec 2022 23:08:54 +0000 (15:08 -0800)
* admin/notes/tree-sitter/build-module/batch.sh:
* admin/notes/tree-sitter/build-module/build.sh: Add cmake support.
* etc/NEWS: Mention it.
* lisp/progmodes/cmake-ts-mode.el: New major mode with
tree-sitter support.
* lisp/progmodes/eglot.el (eglot-server-programs): Add it.

admin/notes/tree-sitter/build-module/batch.sh
admin/notes/tree-sitter/build-module/build.sh
etc/NEWS
lisp/progmodes/cmake-ts-mode.el [new file with mode: 0644]
lisp/progmodes/eglot.el

index 6dce000caa680ff4daebc50fe6ab34cbca557da8..c3954499774a2725ced89cbc7b1a2fe4b46312f0 100755 (executable)
@@ -2,6 +2,7 @@
 
 languages=(
     'c'
+    'cmake'
     'cpp'
     'css'
     'c-sharp'
index cc31e3f6f025d168b762c02faca645a1f19c607b..3467be272b853f43c06005cafe5664fa56b476e9 100755 (executable)
@@ -23,6 +23,9 @@ case "${lang}" in
     "dockerfile")
         namespace="camdencheek"
         ;;
+    "cmake")
+        namespace="uyha"
+        ;;
     "typescript")
         sourcedir="tree-sitter-typescript/typescript/src"
         grammardir="tree-sitter-typescript/typescript"
index 233ef3f5729fd85adfb6e04f796139700818d105..6cb531cd0f141968a1d6d1b6dd1028e6483910de 100644 (file)
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -3073,6 +3073,11 @@ A major mode based on the tree-sitter library for editing
 Dockerfiles.  It includes support for font-locking, indentation, Imenu,
 and which-func.
 
+** New major mode 'cmake-ts-mode'.
+A major mode based on the tree-sitter library for editing CMake files.
+It includes support for font-locking, indentation, Imenu, and
+which-func.
+
 \f
 * Incompatible Lisp Changes in Emacs 29.1
 
diff --git a/lisp/progmodes/cmake-ts-mode.el b/lisp/progmodes/cmake-ts-mode.el
new file mode 100644 (file)
index 0000000..15934a2
--- /dev/null
@@ -0,0 +1,234 @@
+;;; cmake-ts-mode.el --- tree-sitter support for CMake  -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2022 Free Software Foundation, Inc.
+
+;; Author     : Randy Taylor <dev@rjt.dev>
+;; Maintainer : Randy Taylor <dev@rjt.dev>
+;; Created    : December 2022
+;; Keywords   : cmake languages tree-sitter
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs 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.
+
+;; GNU Emacs 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 GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+;;
+
+;;; Code:
+
+(require 'treesit)
+(eval-when-compile (require 'rx))
+
+(declare-function treesit-parser-create "treesit.c")
+(declare-function treesit-induce-sparse-tree "treesit.c")
+(declare-function treesit-node-child "treesit.c")
+(declare-function treesit-node-start "treesit.c")
+(declare-function treesit-node-type "treesit.c")
+
+(defcustom cmake-ts-mode-indent-offset 2
+  "Number of spaces for each indentation step in `cmake-ts-mode'."
+  :version "29.1"
+  :type 'integer
+  :safe 'integerp
+  :group 'cmake)
+
+(defvar cmake-ts-mode--syntax-table
+  (let ((table (make-syntax-table)))
+    (modify-syntax-entry ?#  "<" table)
+    (modify-syntax-entry ?\n ">" table)
+    (modify-syntax-entry ?$  "'" table)
+    table)
+  "Syntax table for `cmake-ts-mode'.")
+
+(defvar cmake-ts-mode--indent-rules
+  `((cmake
+     ((node-is ")") parent-bol 0)
+     ((node-is "else_command") parent-bol 0)
+     ((node-is "elseif_command") parent-bol 0)
+     ((node-is "endforeach_command") parent-bol 0)
+     ((node-is "endfunction_command") parent-bol 0)
+     ((node-is "endif_command") parent-bol 0)
+     ((parent-is "foreach_loop") parent-bol cmake-ts-mode-indent-offset)
+     ((parent-is "function_def") parent-bol cmake-ts-mode-indent-offset)
+     ((parent-is "if_condition") parent-bol cmake-ts-mode-indent-offset)
+     ((parent-is "normal_command") parent-bol cmake-ts-mode-indent-offset)))
+  "Tree-sitter indent rules for `cmake-ts-mode'.")
+
+(defvar cmake-ts-mode--constants
+  '("1" "ON" "TRUE" "YES" "Y" "0" "OFF" "FALSE" "NO" "N" "IGNORE"
+    "NOTFOUND")
+  "CMake constants for tree-sitter font-locking.")
+
+(defvar cmake-ts-mode--keywords
+  '((else) (elseif) (endforeach) (endfunction) (endif) (endmacro)
+    (endwhile) (foreach) (function) (if) (macro) (while))
+  "CMake keywords for tree-sitter font-locking.")
+
+(defvar cmake-ts-mode--foreach-options
+  '("IN" "ITEMS" "LISTS" "RANGE" "ZIP_LISTS")
+  "CMake foreach options for tree-sitter font-locking.")
+
+(defvar cmake-ts-mode--if-conditions
+  '("AND" "COMMAND" "DEFINED" "EQUAL" "EXISTS" "GREATER"
+    "GREATER_EQUAL" "LESS" "LESS_EQUAL" "MATCHES" "NOT" "OR"
+    "PATH_EQUAL" "STREQUAL" "STRGREATER" "STRGREATER_EQUAL" "STRLESS"
+    "STRLESS_EQUAL" "VERSION_EQUAL" "VERSION_GREATER"
+    "VERSION_GREATER_EQUAL" "VERSION_LESS" "VERSION_LESS_EQUAL")
+  "CMake if conditions for tree-sitter font-locking.")
+
+(defvar cmake-ts-mode--font-lock-settings
+  (treesit-font-lock-rules
+   :language 'cmake
+   :feature 'bracket
+   '((["(" ")"]) @font-lock-bracket-face)
+
+   :language 'cmake
+   :feature 'builtin
+   `(((foreach_command
+       ((argument) @font-lock-constant-face
+        (:match ,(rx-to-string
+                  `(seq bol
+                        (or ,@cmake-ts-mode--foreach-options)
+                        eol))
+                @font-lock-constant-face))))
+     ((if_command
+       ((argument) @font-lock-constant-face
+        (:match ,(rx-to-string
+                  `(seq bol
+                        (or ,@cmake-ts-mode--if-conditions)
+                        eol))
+                @font-lock-constant-face)))))
+
+   :language 'cmake
+   :feature 'comment
+   '([(bracket_comment) (line_comment)] @font-lock-comment-face)
+
+   :language 'cmake
+   :feature 'constant
+   `(((argument) @font-lock-constant-face
+      (:match ,(rx-to-string
+                `(seq bol
+                      (or ,@cmake-ts-mode--constants)
+                      eol))
+              @font-lock-constant-face)))
+
+   :language 'cmake
+   :feature 'function
+   '((normal_command (identifier) @font-lock-function-name-face))
+
+   :language 'cmake
+   :feature 'keyword
+   `([,@cmake-ts-mode--keywords] @font-lock-keyword-face)
+
+   :language 'cmake
+   :feature 'number
+   '(((unquoted_argument) @font-lock-number-face
+      (:match "^[[:digit:]]*\\.?[[:digit:]]*\\.?[[:digit:]]+$" @font-lock-number-face)))
+
+   :language 'cmake
+   :feature 'string
+   '([(bracket_argument) (quoted_argument)] @font-lock-string-face)
+
+   :language 'cmake
+   :feature 'escape-sequence
+   :override t
+   '((escape_sequence) @font-lock-escape-face)
+
+   :language 'cmake
+   :feature 'misc-punctuation
+   ;; Don't override strings.
+   :override 'nil
+   '((["$" "{" "}" "<" ">"]) @font-lock-misc-punctuation-face)
+
+   :language 'cmake
+   :feature 'variable
+   :override t
+   '((variable) @font-lock-variable-name-face)
+
+   :language 'cmake
+   :feature 'error
+   :override t
+   '((ERROR) @font-lock-warning-face))
+  "Tree-sitter font-lock settings for `cmake-ts-mode'.")
+
+(defun cmake-ts-mode--imenu ()
+  "Return Imenu alist for the current buffer."
+  (let* ((node (treesit-buffer-root-node))
+         (func-tree (treesit-induce-sparse-tree
+                     node "function_def" nil 1000))
+         (func-index (cmake-ts-mode--imenu-1 func-tree)))
+    (append
+     (when func-index `(("Function" . ,func-index))))))
+
+(defun cmake-ts-mode--imenu-1 (node)
+  "Helper for `cmake-ts-mode--imenu'.
+Find string representation for NODE and set marker, then recurse
+the subtrees."
+  (let* ((ts-node (car node))
+         (children (cdr node))
+         (subtrees (mapcan #'cmake-ts-mode--imenu-1
+                           children))
+         (name (when ts-node
+                 (pcase (treesit-node-type ts-node)
+                   ("function_def"
+                    (treesit-node-text
+                     (treesit-node-child (treesit-node-child ts-node 0) 2) t)))))
+         (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))))))
+
+;;;###autoload
+(add-to-list 'auto-mode-alist
+             '("\\(?:CMakeLists\\.txt\\|\\.cmake\\)$" . cmake-ts-mode))
+
+;;;###autoload
+(define-derived-mode cmake-ts-mode prog-mode "CMake"
+  "Major mode for editing CMake files, powered by tree-sitter."
+  :group 'cmake
+  :syntax-table cmake-ts-mode--syntax-table
+
+  (when (treesit-ready-p 'cmake)
+    (treesit-parser-create 'cmake)
+
+    ;; Comments.
+    (setq-local comment-start "# ")
+    (setq-local comment-end "")
+    (setq-local comment-start-skip (rx "#" (* (syntax whitespace))))
+
+    ;; Imenu.
+    (setq-local imenu-create-index-function #'cmake-ts-mode--imenu)
+    (setq-local which-func-functions nil)
+
+    ;; Indent.
+    (setq-local treesit-simple-indent-rules cmake-ts-mode--indent-rules)
+
+    ;; Font-lock.
+    (setq-local treesit-font-lock-settings cmake-ts-mode--font-lock-settings)
+    (setq-local treesit-font-lock-feature-list
+                '((comment)
+                  (keyword string)
+                  (builtin constant escape-sequence function number variable)
+                  (bracket error misc-punctuation)))
+
+    (treesit-major-mode-setup)))
+
+(provide 'cmake-ts-mode)
+
+;;; cmake-ts-mode.el ends here
index 2427e7b9d31d3311c390331f99da626796cfea61..9c5a361df7bc127b05777b559ad45502b3bf586a 100644 (file)
@@ -183,7 +183,7 @@ chosen (interactively or automatically)."
                       finally (funcall err)))))))
 
 (defvar eglot-server-programs `((rust-mode . ,(eglot-alternatives '("rust-analyzer" "rls")))
-                                (cmake-mode . ("cmake-language-server"))
+                                ((cmake-mode cmake-ts-mode) . ("cmake-language-server"))
                                 (vimrc-mode . ("vim-language-server" "--stdio"))
                                 ((python-mode python-ts-mode)
                                  . ,(eglot-alternatives