branch: elpa/clojure-ts-mode
commit 4eb602cc8b5423cae6491eae3a717ff12891a5e4
Author: dannyfreeman <danny@dfreeman.email>
Commit: dannyfreeman <danny@dfreeman.email>

    Initial commit
    
    Spun off of https://github.com/clojure-emacs/clojure-mode/pull/644
    in clojure-mode, this commit adds a basic readme and work in progress
    clojure-ts-mode. Currently syntax highlighting is working and requires
    tree-sitter-clojure built from this PR:
    https://github.com/sogaiu/tree-sitter-clojure/pull/31
    and emacs built from the emacs-29 branch with tree-sitter installed.
---
 .gitignore         |  11 ++
 README.md          |  21 +++
 clojure-ts-mode.el | 394 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 426 insertions(+)

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000000..7807b63561
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,11 @@
+# temporary files
+*~
+*\#*\#
+*.\#*
+
+# Emacs byte-compiled files
+*.elc
+.cask
+elpa*
+/clojure-ts-mode-autoloads.el
+/clojure-ts-mode-pkg.el
diff --git a/README.md b/README.md
new file mode 100644
index 0000000000..dc40c31393
--- /dev/null
+++ b/README.md
@@ -0,0 +1,21 @@
+[![License GPL 3][badge-license]][copying]
+
+# Clojure Tree-Sitter Mode
+
+`clojure-ts-mode` is an Emacs major mode that provides font-lock (syntax
+highlighting),indentation, and navigation support for the
+[Clojure(Script) programming language](http://clojure.org), powered by the
+[tree-sitter-clojure](https://github.com/sogaiu/tree-sitter-clojure)
+[tree-sitter](https://tree-sitter.github.io/tree-sitter/) grammar.
+
+# Installation
+
+```
+TODO
+```
+
+## License
+
+Copyright © 2022 Danny Freeman and [contributors][].
+
+Distributed under the GNU General Public License; type <kbd>C-h C-c</kbd> to 
view it.
diff --git a/clojure-ts-mode.el b/clojure-ts-mode.el
new file mode 100644
index 0000000000..4b4e328218
--- /dev/null
+++ b/clojure-ts-mode.el
@@ -0,0 +1,394 @@
+;;; clojure-ts-mode.el --- Major mode for Clojure code -*- lexical-binding: t; 
-*-
+
+;; Copyright © 2022-2022 Danny Freeman
+;;
+;; Authors: Danny Freeman <danny@dfreeman.email>
+;; Maintainer: Danny Freeman <danny@dfreeman.email>
+;; URL: http://github.com/clojure-emacs/clojure-ts-mode
+;; Keywords: languages clojure clojurescript lisp
+;; Version: 0.0.1
+;; Package-Requires: ((emacs "29.1"))
+
+;; This file is not part of GNU Emacs.
+
+;;; Commentary:
+
+;; Provides font-lock, indentation, and navigation for the
+;; Clojure programming language (http://clojure.org).
+
+;; Using clojure-ts-mode with paredit or smartparens is highly recommended.
+
+;; Here are some example configurations:
+
+;;   ;; require or autoload paredit-mode
+;;   (add-hook 'clojure-ts-mode-hook #'paredit-mode)
+
+;;   ;; require or autoload smartparens
+;;   (add-hook 'clojure-ts-mode-hook #'smartparens-strict-mode)
+
+;; See inf-clojure (http://github.com/clojure-emacs/inf-clojure) for
+;; basic interaction with Clojure subprocesses.
+
+;; See CIDER (http://github.com/clojure-emacs/cider) for
+;; better interaction with subprocesses via nREPL.
+
+;;; License:
+
+;; 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 GNU Emacs; see the file COPYING.  If not, write to the
+;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+;; Boston, MA 02110-1301, USA.
+
+;;; Code:
+(require 'treesit)
+
+(defconst clojure-ts-mode--builtin-dynamic-var-regexp
+  (eval-and-compile
+    (concat "^"
+            (regexp-opt
+             '("*1" "*2" "*3" "*agent*"
+               "*allow-unresolved-vars*" "*assert*" "*clojure-version*"
+               "*command-line-args*" "*compile-files*"
+               "*compile-path*" "*data-readers*" "*default-data-reader-fn*"
+               "*e" "*err*" "*file*" "*flush-on-newline*"
+               "*in*" "*macro-meta*" "*math-context*" "*ns*" "*out*"
+               "*print-dup*" "*print-length*" "*print-level*"
+               "*print-meta*" "*print-readably*"
+               "*read-eval*" "*source-path*"
+               "*unchecked-math*"
+               "*use-context-classloader*" "*warn-on-reflection*"))
+            "$")))
+
+(defconst clojure-ts-mode--builtin-symbol-regexp
+  (eval-and-compile
+    (concat "^"
+            (regexp-opt
+             '("do"
+               "if"
+               "let*"
+               "var"
+               "fn"
+               "fn*"
+               "loop*"
+               "recur"
+               "throw"
+               "try"
+               "catch"
+               "finally"
+               "set!"
+               "new"
+               "."
+               "monitor-enter"
+               "monitor-exit"
+               "quote"
+
+               "->"
+               "->>"
+               ".."
+               "amap"
+               "and"
+               "areduce"
+               "as->"
+               "assert"
+               "binding"
+               "bound-fn"
+               "case"
+               "comment"
+               "cond"
+               "cond->"
+               "cond->>"
+               "condp"
+               "declare"
+               "delay"
+               "doall"
+               "dorun"
+               "doseq"
+               "dosync"
+               "dotimes"
+               "doto"
+               "extend-protocol"
+               "extend-type"
+               "for"
+               "future"
+               "gen-class"
+               "gen-interface"
+               "if-let"
+               "if-not"
+               "if-some"
+               "import"
+               "in-ns"
+               "io!"
+               "lazy-cat"
+               "lazy-seq"
+               "let"
+               "letfn"
+               "locking"
+               "loop"
+               "memfn"
+               "ns"
+               "or"
+               "proxy"
+               "proxy-super"
+               "pvalues"
+               "refer-clojure"
+               "reify"
+               "some->"
+               "some->>"
+               "sync"
+               "time"
+               "vswap!"
+               "when"
+               "when-first"
+               "when-let"
+               "when-not"
+               "when-some"
+               "while"
+               "with-bindings"
+               "with-in-str"
+               "with-loading-context"
+               "with-local-vars"
+               "with-open"
+               "with-out-str"
+               "with-precision"
+               "with-redefs"
+               "with-redefs-fn"))
+            "$")))
+
+(defface clojure-keyword-face
+  '((t (:inherit font-lock-constant-face)))
+  "Face used to font-lock Clojure keywords (:something).")
+
+(defface clojure-character-face
+  '((t (:inherit font-lock-string-face)))
+  "Face used to font-lock Clojure character literals.")
+
+(defconst clojure--definition-keyword-regexp
+  (rx
+   (or (group line-start (or "ns" "fn") line-end)
+       (group "def"
+              (+ (or alnum
+                     ;; What are valid characters for symbols? is a negative 
match better?
+                     "-" "_" "!" "@" "#" "$" "%" "^" "&" "*" "|" "?" "<" ">" 
"+" "=" ":"))))))
+
+(defconst clojure--variable-keyword-regexp
+  (rx line-start (or "def" "defonce") line-end))
+
+(defconst clojure--type-keyword-regexp
+  (rx line-start (or "defprotocol"
+                     "defmulti"
+                     "deftype"
+                     "defrecord"
+                     "definterface"
+                     "defmethod"
+                     "defstruct")
+      line-end))
+
+(defvar clojure--treesit-settings
+  (treesit-font-lock-rules
+   :feature 'string
+   :language 'clojure
+   '((str_lit) @font-lock-string-face
+     (regex_lit) @font-lock-string-face)
+
+   :feature 'regex
+   :language 'clojure
+   :override t
+   '((regex_lit marker: _ @font-lock-property-face))
+
+   :feature 'number
+   :language 'clojure
+   '((num_lit) @font-lock-number-face)
+
+   :feature 'constant
+   :language 'clojure
+   '([(bool_lit) (nil_lit)] @font-lock-constant-face)
+
+   :feature 'char
+   :language 'clojure
+   '((char_lit) @clojure-character-face)
+
+   ;; :namespace/keyword is highlighted  with the namespace as 
font-lock-type-face
+   ;; and the name clojure-keyword-face
+   ;; I believe in order to do this, the grammer will have to be updated to 
provide these "fields"
+   :feature 'keyword
+   :language 'clojure
+   '((kwd_ns) @font-lock-type-face
+     (kwd_name) @clojure-keyword-face
+     (kwd_lit
+      marker: _ @clojure-keyword-face
+      delimiter: _ :? @default))
+
+   :feature 'builtin
+   :language 'clojure
+   `(((list_lit :anchor (sym_lit (sym_name) @font-lock-keyword-face))
+      (:match ,clojure-ts-mode--builtin-symbol-regexp  
@font-lock-keyword-face))
+     ((sym_name) @font-lock-builtin-face
+      (:match ,clojure-ts-mode--builtin-dynamic-var-regexp 
@font-lock-builtin-face)))
+
+   :feature 'symbol
+   :language 'clojure
+   '((sym_ns) @font-lock-type-face
+     ;; (sym_name) @default
+     ;; (sym_lit delimiter: _ :? @default)
+     )
+
+   ;; How does this work for defns nested in other forms, not at the top level?
+   ;; Should I match against the source node to only hit the top level? Can 
that be expressed?
+   ;; What about valid usages like `(let [closed 1] (defn +closed [n] (+ n 
closed)))'??
+   ;; No wonder the tree-sitter-clojure grammar only touches syntax, and not 
semantics
+   :feature 'definition ;; defn and defn like macros
+   :language 'clojure
+   `(((list_lit :anchor (sym_lit (sym_name) @font-lock-keyword-face)
+                :anchor (sym_lit (sym_name) @font-lock-function-name-face))
+      (:match ,clojure--definition-keyword-regexp
+              @font-lock-keyword-face))
+     ((anon_fn_lit
+       marker: "#" @font-lock-property-face)))
+
+   :feature 'variable ;; def, defonce
+   :language 'clojure
+   `(((list_lit :anchor (sym_lit (sym_name) @font-lock-keyword-face)
+                :anchor (sym_lit (sym_name) @font-lock-variable-name-face))
+      (:match ,clojure--variable-keyword-regexp @font-lock-keyword-face)))
+
+   :feature 'type ;; deftype, defmulti, defprotocol, etc
+   :language 'clojure
+   `(((list_lit :anchor (sym_lit (sym_name) @font-lock-keyword-face)
+                :anchor (sym_lit (sym_name) @font-lock-type-face))
+      (:match ,clojure--type-keyword-regexp @font-lock-keyword-face)))
+
+   :feature 'metadata
+   :language 'clojure
+   :override t
+   `((meta_lit marker: "^" @font-lock-property-face)
+     (meta_lit value: (kwd_lit) @font-lock-property-face) ;; metadata
+     (meta_lit value: (sym_lit (sym_name) @font-lock-type-face)) ;; typehint
+     (old_meta_lit marker: "#^" @font-lock-property-face)
+     (old_meta_lit value: (kwd_lit) @font-lock-property-face) ;; metadata
+     (old_meta_lit value: (sym_lit (sym_name) @font-lock-type-face))) ;; 
typehint
+
+   :feature 'tagged-literals
+   :language 'clojure
+   :override t
+   '((tagged_or_ctor_lit marker: "#" @font-lock-preprocessor-face
+                         tag: (sym_lit) @font-lock-preprocessor-face))
+
+   ;; TODO, also account for `def'
+   ;; Figure out how to highlight symbols in docstrings.
+   :feature 'doc
+   :language 'clojure
+   :override t
+   `(((list_lit :anchor (sym_lit) @def_symbol
+                :anchor (sym_lit) @function_name
+                :anchor (str_lit) @font-lock-doc-face)
+      (:match ,clojure--definition-keyword-regexp @def_symbol)))
+
+   :feature 'quote
+   :language 'clojure
+   '((quoting_lit
+      marker: _ @font-lock-delimiter-face)
+     (var_quoting_lit
+      marker: _ @font-lock-delimiter-face)
+     (syn_quoting_lit
+      marker: _ @font-lock-delimiter-face)
+     (unquoting_lit
+      marker: _ @font-lock-delimiter-face)
+     (unquote_splicing_lit
+      marker: _ @font-lock-delimiter-face)
+     (var_quoting_lit
+      marker: _ @font-lock-delimiter-face))
+
+   :feature 'bracket
+   :language 'clojure
+   '((["(" ")" "[" "]" "{" "}"]) @font-lock-bracket-face
+     (set_lit :anchor "#" @font-lock-bracket-face))
+
+   :feature 'comment
+   :language 'clojure
+   :override t
+   '((comment) @font-lock-comment-face
+     (dis_expr
+      marker: "#_" @font-lock-comment-delimiter-face
+      value: _ @font-lock-comment-face)
+     ((list_lit :anchor (sym_lit (sym_name) @font-lock-comment-delimiter-face))
+      (:match "^comment$" @font-lock-comment-delimiter-face)))
+
+   :feature 'deref ;; not part of clojure-mode, but a cool idea?
+   :language 'clojure
+   '((derefing_lit
+      marker: "@" @font-lock-warning-face))))
+
+;; (defvar clojure-ts-mode--indent-rules
+;;   '((clojure
+;;      ((parent-is "source")
+;;       parent-bol 0)
+
+;;      ((query ((list_lit :anchor (sym_lit))))
+;;       parent-bol 2)
+
+;;      ((or (parent-is "list_lit")
+;;           (parent-is "vec_lit")
+;;           (parent-is "map_lit"))
+;;       parent-bol 1))))
+
+(defvar clojure-ts-mode-map
+  (let ((map (make-sparse-keymap)))
+    ;(set-keymap-parent map clojure-mode-map)
+    map))
+
+;;;###autoload
+(define-derived-mode clojure-ts-mode prog-mode "Clojure[TS]"
+  "Major mode for editing Clojure code.
+Requires Emacs 29 and libtree-sitter-clojure.so available somewhere in
+`treesit-extra-load-path'.
+
+\\{clojure-ts-mode-map}"
+  (when (treesit-ready-p 'clojure)
+    (treesit-parser-create 'clojure)
+    (setq-local treesit-font-lock-settings clojure--treesit-settings)
+    (setq-local treesit-defun-prefer-top-level t
+                treesit-defun-tactic 'top-level
+                treesit-defun-type-regexp (cons (rx (or "list_lit" "vec_lit" 
"map_lit"))
+                                                (lambda (node)
+                                                  (message "Node: %s" 
(treesit-node-text node t))
+                                                  t)))
+    (setq-local treesit-font-lock-feature-list
+                '((comment string char number)
+                  (keyword constant symbol bracket builtin)
+                  (deref quote metadata definition variable type doc regex 
tagged-literals)))
+    ;(setq-local treesit-simple-indent-rules clojure-ts-mode--indent-rules)
+    (treesit-major-mode-setup)
+    ;(clojure-mode-variables)
+    ;(add-hook 'paredit-mode-hook #'clojure-paredit-setup)
+    ;(add-hook 'electric-indent-function 
#'clojure-mode--electric-indent-function)
+    ))
+
+;; We won't autoload this right now, users can opt in by requiring this 
library or adding
+;; clojure-ts-mode to (auto|interpreter)-mode-alist
+(progn
+  (add-hook 'clojure-ts-mode-hook 'treesit-inspect-mode)
+  (add-to-list 'auto-mode-alist
+               '("\\.\\(clj\\|cljd\\|dtm\\|edn\\)\\'" . clojure-ts-mode))
+  ;; TODO: Create clojurec-ts-mode and clojurescript-ts-mode
+  (add-to-list 'auto-mode-alist '("\\.cljc\\'" . clojure-ts-mode))
+  (add-to-list 'auto-mode-alist '("\\.cljs\\'" . clojure-ts-mode))
+  ;; boot build scripts are Clojure source files
+  (add-to-list 'auto-mode-alist '("\\(?:build\\|profile\\)\\.boot\\'" . 
clojure-ts-mode))
+  ;; babashka scripts are Clojure source files
+  (add-to-list 'interpreter-mode-alist '("bb" . clojure-ts-mode))
+  ;; nbb scripts are ClojureScript source files
+  (add-to-list 'interpreter-mode-alist '("nbb" . clojure-ts-mode)))
+
+(provide 'clojure-ts-mode)
+
+;;; clojure-ts-mode.el ends here

Reply via email to