unofficial mirror of emacs-devel@gnu.org 
 help / color / mirror / code / Atom feed
* [ELPA] New package: lua-ts-mode
@ 2023-07-26 16:49 john muhl
  2023-07-26 17:42 ` Theodor Thornhill
  0 siblings, 1 reply; 7+ messages in thread
From: john muhl @ 2023-07-26 16:49 UTC (permalink / raw)
  To: emacs-devel

[-- Attachment #1: Type: text/plain, Size: 1059 bytes --]

I’d like to submit ‘lua-ts-mode’ for inclusion. I’m new to all of this
so suggestions for improvements or anything else are very welcome.

There is already ‘lua-mode’ in NonGNU ELPA but this one is copyright FSF
and uses tree-sitter to provide it’s functionality so I hope the
duplication of effort is not a problem.

Thanks.

URL: https://git.sr.ht/~johnmuhl/lua-ts-mode

;;; Commentary:

This package provides ‘lua-ts-mode’ which is a major mode for
editing Lua files that uses Tree Sitter to parse the language.

This package is compatible with and tested against the grammar
for Lua found at https://github.com/MunifTanjim/tree-sitter-lua

With Git, a C compiler and linker in PATH you can install it by
running:

    M-x treesit-install-language-grammar RET lua RET y
    https://github.com/MunifTanjim/tree-sitter-lua RET RET RET RET RET

To automatically enable it when you open a Lua file add the following
to your ‘user-init-file’:

    (add-to-list 'auto-mode-alist '("\\.lua\\'" . lua-ts-mode))


[-- Attachment #2: lua-ts-mode.el --]
[-- Type: text/plain, Size: 11520 bytes --]

;;; lua-ts-mode.el --- tree sitter support for Lua  -*- lexical-binding: t; -*-

;; Copyright (C) 2023 Free Software Foundation, Inc.

;; Author: John Muhl <jm@pub.pink>
;; Maintainer: John Muhl <jm@pub.pink>
;; Created: June 27, 2023
;; Version: 1.0
;; Keywords: lua languages tree-sitter
;; URL: https://git.sr.ht/~johnmuhl/lua-ts-mode
;; Package-Requires: ((emacs "29.1"))

;; 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:

;; This package provides ‘lua-ts-mode’ which is a major mode for
;; editing Lua files that uses Tree Sitter to parse the language.
;;
;; This package is compatible with and tested against the grammar
;; for Lua found at https://github.com/MunifTanjim/tree-sitter-lua
;;
;; With Git, a C compiler and linker in PATH you can install it by
;; running:
;;
;;     M-x treesit-install-language-grammar RET lua RET y
;;     https://github.com/MunifTanjim/tree-sitter-lua RET RET RET RET RET
;;
;; To automatically enable it when you open a Lua file add the following
;; to your ‘user-init-file’:
;;
;;     (add-to-list 'auto-mode-alist '("\\.lua\\'" . lua-ts-mode))

;;; News

;; Version 1.0 <2023-07-11 Tue>
;; Supports font-lock, indentation, navigation, imenu,
;; which-function-mode and outline-minor-mode.

;;; Code:

(require 'treesit)
(eval-when-compile (require 'rx))

(declare-function treesit-node-child-by-field-name "treesit.c")
(declare-function treesit-node-type "treesit.c")
(declare-function treesit-parser-create "treesit.c")
(declare-function treesit-search-subtree "treesit.c")

(defcustom lua-ts-mode-indent-offset 4
  "Number of spaces for each indentation step in `lua-ts-mode'."
  :type 'integer
  :safe 'integerp
  :group 'lua)

(defvar lua-ts-mode--builtins
  '("assert" "collectgarbage" "coroutine" "debug" "dofile"
    "error" "getmetatable" "io" "ipairs" "load" "loadfile"
    "math" "next" "os" "package" "pairs" "pcall" "print"
    "rawequal" "rawget" "rawlen" "rawset" "require" "select"
    "setmetatable" "string" "table" "tonumber" "tostring"
    "type" "utf8" "warn" "xpcall")
  "Lua built-in functions for tree-sitter font-locking.")

(defvar lua-ts-mode--syntax-table
  (let ((table (make-syntax-table)))
    (modify-syntax-entry ?+  "."    table)
    (modify-syntax-entry ?-  ". 12" table)
    (modify-syntax-entry ?=  "."    table)
    (modify-syntax-entry ?%  "."    table)
    (modify-syntax-entry ?^  "."    table)
    (modify-syntax-entry ?~  "."    table)
    (modify-syntax-entry ?<  "."    table)
    (modify-syntax-entry ?>  "."    table)
    (modify-syntax-entry ?/  "."    table)
    (modify-syntax-entry ?*  "."    table)
    (modify-syntax-entry ?\n ">"    table)
    (modify-syntax-entry ?\' "\""   table)
    (modify-syntax-entry ?\" "\""   table)
    table)
  "Syntax table for `lua-ts-mode'.")

(defvar lua-ts-mode--font-lock-settings
  (treesit-font-lock-rules
   :language 'lua
   :feature 'bracket
   '(["(" ")" "[" "]" "{" "}"] @font-lock-bracket-face)

   :language 'lua
   :feature 'builtin
   `(((identifier) @font-lock-builtin-face
      (:match ,(rx-to-string
                `(seq bol (or ,@lua-ts-mode--builtins) eol))
              @font-lock-builtin-face)))

   :language 'lua
   :feature 'delimiter
   '(["," ";"] @font-lock-delimiter-face)

   :language 'lua
   :feature 'escape
   '((escape_sequence) @font-lock-escape-face)

   :language 'lua
   :feature 'function
   '((function_call name: (identifier) @font-lock-function-call-face)
     (function_call
      name: (method_index_expression
             method: (identifier) @font-lock-function-call-face))
     (function_call
	  name: (dot_index_expression
             table: (identifier) @font-lock-function-call-face)))

   :language 'lua
   :feature 'constant
   '((variable_list
      attribute: (attribute (["<" ">"] (identifier))))
     @font-lock-constant-face)

   :language 'lua
   :feature 'operator
   '(["and" "not" "or" "+" "-" "*" "/" "%" "^"
      "#" "==" "~=" "<=" ">=" "<" ">" "=" "&"
      "~" "|" "<<" ">>" "//" ".."]
     @font-lock-operator-face
     (vararg_expression) @font-lock-operator-face)

   :language 'lua
   :feature 'property
   '((field name: (identifier) @font-lock-property-name-face)
     (dot_index_expression
      field: (identifier) @font-lock-property-use-face))

   :language 'lua
   :feature 'punctuation
   '(["." ":"] @font-lock-punctuation-face)

   :language 'lua
   :feature 'variable
   '((function_call
      arguments: (arguments (identifier))
      @font-lock-variable-use-face)
     (function_call
      name: (method_index_expression
             table: (identifier) @font-lock-variable-use-face))
     (goto_statement (identifier) @font-lock-variable-use-face))

   :language 'lua
   :feature 'assignment
   '((variable_list (identifier) @font-lock-variable-name-face))

   :language 'lua
   :feature 'number
   '((number) @font-lock-number-face)

   :language 'lua
   :feature 'keyword
   '((break_statement) @font-lock-keyword-face
     (true) @font-lock-constant-face
     (false) @font-lock-constant-face
     (nil) @font-lock-constant-face
     ["and" "do" "else" "elseif" "end" "for" "function"
      "goto" "if" "in" "local" "not" "or" "repeat"
      "return" "then" "until" "while"]
     @font-lock-keyword-face)

   :language 'lua
   :feature 'string
   '((string) @font-lock-string-face)

   :language 'lua
   :feature 'comment
   '((comment) @font-lock-comment-face
     (hash_bang_line) @font-lock-comment-face)

   :language 'lua
   :feature 'definition
   '((function_declaration
      name: (identifier) @font-lock-function-name-face)
     (parameters
      name: (identifier) @font-lock-variable-name-face)
     (label_statement) @font-lock-variable-name-face)

   :language 'lua
   :feature 'error
   :override t
   '((ERROR) @font-lock-warning-face))
  "Tree-sitter font-lock settings for `lua-ts-mode'.")

(defvar lua-ts-mode--indent-rules
  `((lua
     ((parent-is "chunk") column-0 0)
     ((node-is "comment_end") column-0 0)
     ((parent-is "block") parent-bol 0)
     ((node-is "}") parent-bol 0)
     ((node-is ")") parent-bol 0)
     ((node-is "else_statement") parent-bol 0)
     ((node-is "elseif_statement") parent-bol 0)
     ((node-is "end") parent-bol 0)
     ((node-is "until") parent-bol 0)
     ((parent-is "for_statement") parent-bol lua-ts-mode-indent-offset)
     ((parent-is "function_declaration") parent-bol lua-ts-mode-indent-offset)
     ((parent-is "function_definition") parent-bol lua-ts-mode-indent-offset)
     ((parent-is "if_statement") parent-bol lua-ts-mode-indent-offset)
     ((parent-is "else_statement") parent-bol lua-ts-mode-indent-offset)
     ((parent-is "repeat_statement") parent-bol lua-ts-mode-indent-offset)
     ((parent-is "while_statement") parent-bol lua-ts-mode-indent-offset)
     ((parent-is "table_constructor") parent-bol lua-ts-mode-indent-offset)
     ((parent-is "arguments") parent-bol lua-ts-mode-indent-offset)
     ((parent-is "parameters") parent-bol lua-ts-mode-indent-offset)
     ((parent-is "ERROR") no-indent 0))))

(defun lua-ts-mode--defun-name (node)
  "Return the defun name of NODE.
Return nil if there is no name or if NODE is not a defun node."
  (pcase (treesit-node-type node)
    ((or "function_declaration" "function_definition")
     (treesit-node-text
      (treesit-node-child-by-field-name node "name") t))
    ("variable_declaration"
     (let ((child (treesit-node-child-by-field-name node "name")))
       (if child (treesit-node-text child t)
         (treesit-node-text
          (treesit-node-child-by-field-name
           (treesit-search-subtree node "assignment_statement" nil nil 1)
           "name")
          t))))
    ("field"
     (and (treesit-search-subtree node "function_definition" nil nil 1)
          (treesit-node-text
           (treesit-node-child-by-field-name node "name") t)))))

;;;###autoload
(define-derived-mode lua-ts-mode prog-mode "Lua"
  "Major mode for editing Lua, powered by tree-sitter."
  :group 'lua
  :syntax-table lua-ts-mode--syntax-table

  (when (treesit-ready-p 'lua)
    (treesit-parser-create 'lua)

    (setq-local treesit-defun-prefer-top-level t)

    ;; Comments.
    (setq-local comment-start "--")
    (setq-local comment-start-skip (rx "--" (* (syntax whitespace))))
    (setq-local comment-end "")

    ;; Font-lock.
    (setq-local treesit-font-lock-settings lua-ts-mode--font-lock-settings)
    (setq-local treesit-font-lock-feature-list
                '((comment definition)
                  (builtin keyword string)
                  (assignment constant number)
                  (bracket
                   delimiter
                   escape
                   function
                   operator
                   property
                   punctuation
                   variable)))

    ;; Indent.
    (setq-local treesit-simple-indent-rules lua-ts-mode--indent-rules)

    ;; Navigation.
    (setq-local treesit-defun-name-function #'lua-ts-mode--defun-name)
    (setq-local treesit-defun-type-regexp
                (regexp-opt '("function_declaration"
                              "function_definition")))
    (setq-local treesit-sentence-type-regexp
                (regexp-opt '("do_statement"
                              "while_statement"
                              "repeat_statement"
                              "if_statement"
                              "for_statement"
                              "variable_declaration")))
    (setq-local treesit-sexp-type-regexp
                (regexp-opt '("arguments"
                              "comment"
                              "string"
                              "table_constructor")))

    ;; Imenu.
    (setq-local treesit-simple-imenu-settings
                `(("Variable" "\\`variable_declaration\\'" nil nil)
                  ("Function" ,(rx bos (or "function_declaration"
                                           "function_definition"
                                           "field")
                                   eos)
                   nil nil)))

    ;; Which-function.
    (setq-local which-func-functions (treesit-defun-at-point))

    ;; Outline.
    (setq-local outline-regexp
                (regexp-opt '("do" "for" "function" "local"
                              "if" "repeat" "while" "--[[")))

    (treesit-major-mode-setup)))

(if (treesit-ready-p 'lua)
    (add-to-list 'auto-mode-alist '("\\.lua\\'" . lua-ts-mode)))

(provide 'lua-ts-mode)

;;; lua-ts-mode.el ends here

;; Local Variables:
;; indent-tabs-mode: nil
;; sentence-end-double-space: t
;; End:

^ permalink raw reply	[flat|nested] 7+ messages in thread

end of thread, other threads:[~2023-07-29 10:46 UTC | newest]

Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2023-07-26 16:49 [ELPA] New package: lua-ts-mode john muhl
2023-07-26 17:42 ` Theodor Thornhill
2023-07-27  4:43   ` Eli Zaretskii
2023-07-27 13:34     ` john muhl
2023-07-27 19:06       ` Eli Zaretskii
2023-07-27 21:52         ` john muhl
2023-07-29 10:46           ` Eli Zaretskii

Code repositories for project(s) associated with this public inbox

	https://git.savannah.gnu.org/cgit/emacs.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).