[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[nongnu] elpa/clojure-ts-mode 9af0a6b35c 1/2: Use nested markdown gramma
From: |
ELPA Syncer |
Subject: |
[nongnu] elpa/clojure-ts-mode 9af0a6b35c 1/2: Use nested markdown grammar for highlighting docstrings |
Date: |
Sat, 9 Sep 2023 12:59:34 -0400 (EDT) |
branch: elpa/clojure-ts-mode
commit 9af0a6b35c708309acdfeb4c0c79061b0fd4eb44
Author: Danny Freeman <danny@dfreeman.email>
Commit: Danny Freeman <danny@dfreeman.email>
Use nested markdown grammar for highlighting docstrings
This uses the "inline" version of the markdown grammar.
https://github.com/MDeiml/tree-sitter-markdown
see issue #18
---
CHANGELOG.md | 3 +
README.md | 13 +-
clojure-ts-mode.el | 435 +++++++++++++++++++++++++++++----------------------
test/indentation.clj | 4 +-
4 files changed, 260 insertions(+), 195 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b45192d0b0..59fedbda22 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,6 +8,9 @@
- (See treesit-font-lock-level documentation for more information.)
- Highlight docstrings in interface, protocol, and variable definitions
- Add support for semantic indentation (now the default)
+- Highlight "\`quoted-symbols\` in docs strings like this."
+ - This feature uses a nested markdown parser.
+ If the parser is not available this feature should be silently disabled.
## 0.1.5
diff --git a/README.md b/README.md
index dde2547a13..9dc5438ff8 100644
--- a/README.md
+++ b/README.md
@@ -106,11 +106,14 @@ git clone
https://github.com/clojure-emacs/clojure-ts-mode.git
Once installed, evaluate clojure-ts-mode.el and you should be ready to go.
-### Install libtree-sitter-clojure shared library
+### Install tree-sitter grammars
+
+The compile tree-sitter clojure shared library must be available to Emacs.
+Additionally, the tree-sitter
[markdown_inline](https://github.com/MDeiml/tree-sitter-markdown) shared
library will also be used for docstrings if available.
-The tree-sitter clojure shared library must be available to Emacs.
If you have `git` and a C compiler (`cc`) available on your system's `PATH`,
**then these steps should not be necessary**.
-clojure-ts-mode will install the grammar when you first open a Clojure file.
+clojure-ts-mode will install the grammars when you first open a Clojure file
and
+`clojure-ts-ensure-grammars` is set to `t` (the default).
If clojure-ts-mode fails to automatically install the grammar, you have the
option to install it manually.
@@ -120,7 +123,9 @@ Some distributions may package the tree-sitter-clojure
grammar in their package
If yours does you may be able to install tree-sitter-clojure with your system
package manager.
If the version packaged by your OS is out of date, you may see errors in the
`*Messages*` buffer or your clojure buffers will not have any syntax
highlighting.
-If this happens you should install the grammar manually with `M-x
treesit-install-language-grammar <RET> clojure`.
+
+If this happens you should install the grammar manually with `M-x
treesit-install-language-grammar <RET> clojure` and follow the prompts.
+Recommended values for these prompts can be seen in
`clojure-ts-grammar-recipes`.
#### Compile From Source
diff --git a/clojure-ts-mode.el b/clojure-ts-mode.el
index b2554e82bb..eb267b7a25 100644
--- a/clojure-ts-mode.el
+++ b/clojure-ts-mode.el
@@ -86,6 +86,12 @@ itself."
:type 'boolean
:package-version '(clojure-ts-mode . "0.1.3"))
+(defcustom clojure-ts-ensure-grammars t
+ "When non-nil, ensure required tree-sitter grammars are installed."
+ :safe #'booleanp
+ :type 'boolean
+ :package-version '(clojure-ts-mode . "0.2.0"))
+
(defvar clojure-ts--debug nil
"Enables debugging messages, shows current node in mode-line.
Only intended for use at development time.")
@@ -233,167 +239,188 @@ Only intended for use at development time.")
(rx line-start (or "defprotocol" "definterface") line-end))
"A regular expression matching a symbol used to define an interface.")
-(defun clojure-ts--font-lock-settings ()
- "Return font lock settings suitable for use in `treesit-font-lock-settings'."
- (treesit-font-lock-rules
- :feature 'string
- :language 'clojure
- '((str_lit) @font-lock-string-face
- (regex_lit) @font-lock-regexp-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-ts-character-face)
-
- :feature 'keyword
- :language 'clojure
- '((kwd_ns) @font-lock-type-face
- (kwd_name) @clojure-ts-keyword-face
- (kwd_lit
- marker: _ @clojure-ts-keyword-face
- delimiter: _ :? @default))
-
- :feature 'builtin
- :language 'clojure
- `(((list_lit :anchor (sym_lit (sym_name) @font-lock-keyword-face))
- (:match ,clojure-ts--builtin-symbol-regexp @font-lock-keyword-face))
- ((sym_name) @font-lock-builtin-face
- (:match ,clojure-ts--builtin-dynamic-var-regexp
@font-lock-builtin-face)))
-
- ;; Any function calls, not built-ins.
- ;; This can give false positives (macros, quoted lists, namespace imports)
- ;; but is a level 4 feature and never enabled by default.
- :feature 'function
- :language 'clojure
- '((list_lit :anchor (sym_lit (sym_name) @font-lock-function-call-face)))
-
- :feature 'symbol
- :language 'clojure
- '((sym_ns) @font-lock-type-face)
-
- ;; 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) @def)
- :anchor (sym_lit (sym_name) @font-lock-function-name-face))
- (:match ,clojure-ts--definition-symbol-regexp @def))
- ((anon_fn_lit
- marker: "#" @font-lock-property-face)))
-
- :feature 'variable ;; def, defonce
- :language 'clojure
- `(((list_lit :anchor (sym_lit (sym_name) @def)
- :anchor (sym_lit (sym_name) @font-lock-variable-name-face))
- (:match ,clojure-ts--variable-definition-symbol-regexp @def)))
-
- ;; Can we support declarations in the namespace form?
- :feature 'type
- :language 'clojure
- `(;; Type Declarations
- ((list_lit :anchor (sym_lit (sym_name) @def)
- :anchor (sym_lit (sym_name) @font-lock-type-face))
- (:match ,clojure-ts--typedef-symbol-regexp @def))
- ;; Type Hints
- (meta_lit
- marker: "^" @font-lock-operator-face
- value: (sym_lit (sym_name) @font-lock-type-face))
- (old_meta_lit
- marker: "#^" @font-lock-operator-face
- value: (sym_lit (sym_name) @font-lock-type-face)))
-
- :feature 'metadata
- :language 'clojure
- :override t
- `((meta_lit
- marker: "^" @font-lock-operator-face
- value: (kwd_lit (kwd_name) @font-lock-property-name-face))
- (old_meta_lit
- marker: "#^" @font-lock-operator-face
- value: (kwd_lit (kwd_name) @font-lock-property-name-face)))
-
- :feature 'tagged-literals
- :language 'clojure
- :override t
- '((tagged_or_ctor_lit marker: "#" @font-lock-preprocessor-face
- tag: (sym_lit) @font-lock-preprocessor-face))
-
- ;; Figure out how to highlight symbols in docstrings.
- ;; Might require a markdown grammar
- :feature 'doc
- :language 'clojure
- :override t
- `(;; Captures docstrings in def, defonce
- ((list_lit :anchor (sym_lit) @def_symbol
- :anchor (sym_lit) ; variable name
- :anchor (str_lit) @font-lock-doc-face
- :anchor (_)) ; the variable's value
- (:match ,clojure-ts--variable-definition-symbol-regexp @def_symbol))
- ;; Captures docstrings defn, defmacro, ns, and things like that
- ((list_lit :anchor (sym_lit) @def_symbol
- :anchor (sym_lit) ; function_name
- :anchor (str_lit) @font-lock-doc-face)
- (:match ,clojure-ts--definition-symbol-regexp @def_symbol))
- ;; Captures docstrings in defprotcol, definterface
- ((list_lit :anchor (sym_lit) @def_symbol
- (list_lit
- :anchor (sym_lit) (vec_lit) :*
- (str_lit) @font-lock-doc-face :anchor)
- :*)
- (:match ,clojure-ts--interface-def-symbol-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)
- (,(append
- '(list_lit :anchor (sym_lit) @font-lock-comment-delimiter-face)
- (when clojure-ts-comment-macro-font-lock-body
- '(_ :* @font-lock-comment-face)))
- (:match "^\\(\\(clojure.core/\\)?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))))
+(defun clojure-ts--docstring-query (capture-symbol)
+ "Return a query that captures docstrings with CAPTURE-SYMBOL."
+ `(;; Captures docstrings in def, defonce
+ ((list_lit :anchor (sym_lit) @def_symbol
+ :anchor (sym_lit) ; variable name
+ :anchor (str_lit) ,capture-symbol
+ :anchor (_)) ; the variable's value
+ (:match ,clojure-ts--variable-definition-symbol-regexp @def_symbol))
+ ;; Captures docstrings defn, defmacro, ns, and things like that
+ ((list_lit :anchor (sym_lit) @def_symbol
+ :anchor (sym_lit) ; function_name
+ :anchor (str_lit) ,capture-symbol)
+ (:match ,clojure-ts--definition-symbol-regexp @def_symbol))
+ ;; Captures docstrings in defprotcol, definterface
+ ((list_lit :anchor (sym_lit) @def_symbol
+ (list_lit
+ :anchor (sym_lit) (vec_lit) :*
+ (str_lit) ,capture-symbol :anchor)
+ :*)
+ (:match ,clojure-ts--interface-def-symbol-regexp @def_symbol))))
+
+(defvar clojure-ts--treesit-range-settings
+ (treesit-range-rules
+ :embed 'markdown_inline
+ :host 'clojure
+ (clojure-ts--docstring-query '@capture)))
+
+(defun clojure-ts--font-lock-settings (markdown-available)
+ "Return font lock settings suitable for use in `treesit-font-lock-settings'.
+When MARKDOWN-AVAILABLE is non-nil, includes rules for highlighting docstrings
+with the markdown_inline grammar."
+ (append
+ (treesit-font-lock-rules
+ :feature 'string
+ :language 'clojure
+ '((str_lit) @font-lock-string-face
+ (regex_lit) @font-lock-regexp-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-ts-character-face)
+
+ :feature 'keyword
+ :language 'clojure
+ '((kwd_ns) @font-lock-type-face
+ (kwd_name) @clojure-ts-keyword-face
+ (kwd_lit
+ marker: _ @clojure-ts-keyword-face
+ delimiter: _ :? @default))
+
+ :feature 'builtin
+ :language 'clojure
+ `(((list_lit :anchor (sym_lit (sym_name) @font-lock-keyword-face))
+ (:match ,clojure-ts--builtin-symbol-regexp @font-lock-keyword-face))
+ ((sym_name) @font-lock-builtin-face
+ (:match ,clojure-ts--builtin-dynamic-var-regexp
@font-lock-builtin-face)))
+
+ ;; Any function calls, not built-ins.
+ ;; This can give false positives (macros, quoted lists, namespace imports)
+ ;; but is a level 4 feature and never enabled by default.
+ :feature 'function
+ :language 'clojure
+ '((list_lit :anchor (sym_lit (sym_name) @font-lock-function-call-face)))
+
+ :feature 'symbol
+ :language 'clojure
+ '((sym_ns) @font-lock-type-face)
+
+ ;; 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) @def)
+ :anchor (sym_lit (sym_name) @font-lock-function-name-face))
+ (:match ,clojure-ts--definition-symbol-regexp @def))
+ ((anon_fn_lit
+ marker: "#" @font-lock-property-face)))
+
+ :feature 'variable ;; def, defonce
+ :language 'clojure
+ `(((list_lit :anchor (sym_lit (sym_name) @def)
+ :anchor (sym_lit (sym_name) @font-lock-variable-name-face))
+ (:match ,clojure-ts--variable-definition-symbol-regexp @def)))
+
+ ;; Can we support declarations in the namespace form?
+ :feature 'type
+ :language 'clojure
+ `(;; Type Declarations
+ ((list_lit :anchor (sym_lit (sym_name) @def)
+ :anchor (sym_lit (sym_name) @font-lock-type-face))
+ (:match ,clojure-ts--typedef-symbol-regexp @def))
+ ;; Type Hints
+ (meta_lit
+ marker: "^" @font-lock-operator-face
+ value: (sym_lit (sym_name) @font-lock-type-face))
+ (old_meta_lit
+ marker: "#^" @font-lock-operator-face
+ value: (sym_lit (sym_name) @font-lock-type-face)))
+
+ :feature 'metadata
+ :language 'clojure
+ :override t
+ `((meta_lit
+ marker: "^" @font-lock-operator-face
+ value: (kwd_lit (kwd_name) @font-lock-property-name-face))
+ (old_meta_lit
+ marker: "#^" @font-lock-operator-face
+ value: (kwd_lit (kwd_name) @font-lock-property-name-face)))
+
+ :feature 'tagged-literals
+ :language 'clojure
+ :override t
+ '((tagged_or_ctor_lit marker: "#" @font-lock-preprocessor-face
+ tag: (sym_lit) @font-lock-preprocessor-face))
+
+ :feature 'doc
+ :language 'clojure
+ :override t
+ (clojure-ts--docstring-query '@font-lock-doc-face))
+
+ (when markdown-available
+ (treesit-font-lock-rules
+ :feature 'doc
+ :language 'markdown_inline
+ :override t
+ `((inline
+ (code_span (code_span_delimiter) :* @font-lock-delimiter-face)
+ @font-lock-constant-face))))
+
+ (treesit-font-lock-rules
+ :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)
+ (,(append
+ '(list_lit :anchor (sym_lit) @font-lock-comment-delimiter-face)
+ (when clojure-ts-comment-macro-font-lock-body
+ '(_ :* @font-lock-comment-face)))
+ (:match "^\\(\\(clojure.core/\\)?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)))))
;; Node predicates
@@ -767,46 +794,74 @@ forms like deftype, defrecord, reify, proxy, etc."
(set-keymap-parent map clojure-ts-mode-map)
map))
-;;;###autolaod
-(add-to-list
- 'treesit-language-source-alist
- '(clojure "https://github.com/sogaiu/tree-sitter-clojure.git" "v0.0.12"))
-
(defun clojure-ts-mode-display-version ()
"Display the current `clojure-mode-version' in the minibuffer."
(interactive)
(message "clojure-ts-mode (version %s)" clojure-ts-mode-version))
+(defconst clojure-ts-grammar-recipes
+ '((clojure "https://github.com/sogaiu/tree-sitter-clojure.git"
+ "v0.0.12")
+ (markdown_inline "https://github.com/MDeiml/tree-sitter-markdown"
+ "v0.1.6"
+ "tree-sitter-markdown-inline/src"))
+ "Intended to be used as the value for `treesit-language-source-alist'.")
+
+(defun clojure-ts--ensure-grammars ()
+ "Install required language grammars if not already available."
+ (when clojure-ts-ensure-grammars
+ (let ((treesit-language-source-alist clojure-ts-grammar-recipes))
+ (unless (treesit-language-available-p 'clojure nil)
+ (message "Installing clojure tree-sitter grammar.")
+ (treesit-install-language-grammar 'clojure))
+ (unless (treesit-language-available-p 'markdown_inline nil)
+ (message "Installing markdown tree-sitter grammar.")
+ (treesit-install-language-grammar 'markdown_inline)))))
+
+(defun clojure-ts-mode-variables (&optional markdown-available)
+ "Set up initial buffer-local variables for clojure-ts-mode.
+See `clojure-ts--font-lock-settings' for usage of MARKDOWN-AVAILABLE."
+ (setq-local comment-start ";")
+ (setq-local treesit-font-lock-settings
+ (clojure-ts--font-lock-settings markdown-available))
+ (setq-local treesit-defun-prefer-top-level t)
+ (setq-local treesit-defun-tactic 'top-level)
+ (setq-local treesit-defun-type-regexp
+ (rx (or "list_lit" "vec_lit" "map_lit")))
+ (setq-local treesit-simple-indent-rules
+ (clojure-ts--configured-indent-rules))
+ (setq-local treesit-defun-name-function
+ #'clojure-ts--standard-definition-node-name)
+ (setq-local treesit-simple-imenu-settings
+ clojure-ts--imenu-settings)
+ (setq-local treesit-font-lock-feature-list
+ '((comment definition variable)
+ (keyword string char symbol builtin type)
+ (constant number quote metadata doc)
+ (bracket deref function regex tagged-literals)))
+ (when (boundp 'treesit-thing-settings) ;; Emacs 30+
+ (setq-local treesit-thing-settings clojure-ts--thing-settings)))
+
;;;###autoload
(define-derived-mode clojure-ts-mode prog-mode "Clojure[TS]"
"Major mode for editing Clojure code.
\\{clojure-ts-mode-map}"
:syntax-table clojure-ts-mode-syntax-table
- (unless (treesit-language-available-p 'clojure nil)
- (treesit-install-language-grammar 'clojure))
- (setq-local comment-start ";")
- (when (treesit-ready-p 'clojure)
- (treesit-parser-create 'clojure)
- (setq-local treesit-font-lock-settings (clojure-ts--font-lock-settings)
- treesit-defun-prefer-top-level t
- treesit-defun-tactic 'top-level
- treesit-defun-type-regexp (rx (or "list_lit" "vec_lit"
"map_lit"))
- treesit-simple-indent-rules
(clojure-ts--configured-indent-rules)
- treesit-defun-name-function
#'clojure-ts--standard-definition-node-name
- treesit-simple-imenu-settings clojure-ts--imenu-settings
- treesit-font-lock-feature-list
- '((comment definition variable)
- (keyword string char symbol builtin type)
- (constant number quote metadata doc)
- (bracket deref function regex tagged-literals)))
- (when (boundp 'treesit-thing-settings) ;; Emacs 30+
- (setq-local treesit-thing-settings clojure-ts--thing-settings))
- (when clojure-ts--debug
- (setq-local treesit--indent-verbose t
- treesit--font-lock-verbose t)
- (treesit-inspect-mode))
- (treesit-major-mode-setup)))
+ (clojure-ts--ensure-grammars)
+ (let ((markdown-available (treesit-ready-p 'markdown_inline t)))
+ (when markdown-available
+ (treesit-parser-create 'markdown_inline)
+ (setq-local treesit-range-settings clojure-ts--treesit-range-settings))
+ (when (treesit-ready-p 'clojure)
+ (treesit-parser-create 'clojure)
+ (clojure-ts-mode-variables markdown-available)
+ (when clojure-ts--debug
+ (setq-local treesit--indent-verbose t)
+ (when (eq clojure-ts--debug 'font-lock)
+ (setq-local treesit--font-lock-verbose t))
+ (treesit-inspect-mode))
+ (treesit-major-mode-setup))))
;;;###autoload
(define-derived-mode clojurescript-ts-mode clojure-ts-mode "ClojureScript[TS]"
diff --git a/test/indentation.clj b/test/indentation.clj
index 520adc9987..a5fe041f0c 100644
--- a/test/indentation.clj
+++ b/test/indentation.clj
@@ -1,4 +1,5 @@
(ns indentation
+ "Docstring `important`. asdf"
(:require
[clojure.string :as str])
(:import
@@ -79,7 +80,8 @@
(defprotocol IProto
- (foo [this x])
+ (foo [this x]
+ "`this` is a docstring.")
(bar [this y]))
(deftype MyThing []