Skip to content

Commit

Permalink
Fix ns docstring highlighting regression
Browse files Browse the repository at this point in the history
This also begins the process of making our symbol matching regular
expressions user extensible (see issue #15). There are more to convert
from regexps to normal lists, but I want to take my time and make sure I
get the names down correctly. It is important to get maximum reuse so
users don't have to add their fancy def-whatever to 4 different lists.
  • Loading branch information
dannyfreeman committed Sep 15, 2023
1 parent 6ea4196 commit ec48877
Show file tree
Hide file tree
Showing 2 changed files with 112 additions and 27 deletions.
72 changes: 45 additions & 27 deletions clojure-ts-mode.el
Original file line number Diff line number Diff line change
Expand Up @@ -202,17 +202,26 @@ Only intended for use at development time.")
'((t (:inherit font-lock-string-face)))
"Face used to font-lock Clojure character literals.")

(defconst clojure-ts--definition-symbol-regexp
(rx
line-start
(or (group "fn")
(group "def"
(+ (or alnum
;; What are valid characters for symbols?
;; is a negative match better?
"-" "_" "!" "@" "#" "$" "%" "^" "&"
"*" "|" "?" "<" ">" "+" "=" ":"))))
line-end))
(defun clojure-ts-symbol-regexp (symbols)
"Return a regular expression that matches one of SYMBOLS exactly."
(concat "^" (regexp-opt symbols) "$"))

(defvar clojure-ts-function-docstring-symbols
'("definline"
"defmulti"
"defmacro"
"defn"
"defn-"
"defprotocol"
"ns")
"Symbols that accept an optional docstring as their second argument.")

(defvar clojure-ts-definition-docstring-symbols
'("def")
"Symbols that accept an optional docstring as their second argument.
Any symbols added here should only treat their second argument as a docstring
if a third argument (the value) is provided.
\"def\" is the only builtin Clojure symbol that behaves like this.")

(defconst clojure-ts--variable-definition-symbol-regexp
(eval-and-compile
Expand Down Expand Up @@ -244,40 +253,49 @@ Only intended for use at development time.")

(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
`(;; Captures docstrings in def
((list_lit :anchor (sym_lit) @_def_symbol
:anchor (comment) :?
:anchor (sym_lit) ; variable name
:anchor (comment) :?
:anchor (str_lit) ,capture-symbol
:anchor (_)) ; the variable's value
(:match ,clojure-ts--variable-definition-symbol-regexp @def_symbol))
(:match ,(clojure-ts-symbol-regexp clojure-ts-definition-docstring-symbols)
@_def_symbol))
;; Captures docstrings in metadata of definitions
((list_lit :anchor (sym_lit) @def_symbol
((list_lit :anchor (sym_lit) @_def_symbol
:anchor (comment) :?
:anchor (sym_lit
(meta_lit
value: (map_lit
(kwd_lit) @doc-keyword
(kwd_lit) @_doc-keyword
:anchor
(str_lit) ,capture-symbol))))
;; We're only supporting this on a fixed set of defining symbols
;; Existing regexes don't encompass def and defn
;; Naming another regex is very cumbersome.
(:match ,(regexp-opt '("def" "defonce" "defn" "defn-" "defmacro" "ns"
"defmulti" "definterface" "defprotocol"
"deftype" "defrecord" "defstruct"))
@def_symbol)
(:equal @doc-keyword ":doc"))
(:match ,(clojure-ts-symbol-regexp
'("def" "defonce" "defn" "defn-" "defmacro" "ns"
"defmulti" "definterface" "defprotocol"
"deftest" "deftest-"
"deftype" "defrecord" "defstruct"))
@_def_symbol)
(:equal @_doc-keyword ":doc"))
;; Captures docstrings defn, defmacro, ns, and things like that
((list_lit :anchor (sym_lit) @def_symbol
((list_lit :anchor (sym_lit) @_def_symbol
:anchor (comment) :?
:anchor (sym_lit) ; function_name
:anchor (comment) :?
:anchor (str_lit) ,capture-symbol)
(:match ,clojure-ts--definition-symbol-regexp @def_symbol))
(:match ,(clojure-ts-symbol-regexp clojure-ts-function-docstring-symbols)
@_def_symbol))
;; Captures docstrings in defprotcol, definterface
((list_lit :anchor (sym_lit) @def_symbol
((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))))
(:match ,clojure-ts--interface-def-symbol-regexp @_def_symbol))))

(defvar clojure-ts--treesit-range-settings
(treesit-range-rules
Expand Down Expand Up @@ -752,7 +770,7 @@ forms like deftype, defrecord, reify, proxy, etc."
(and (treesit-node-eq node (treesit-node-child parent 2 t))
(let ((first-auncle (treesit-node-child parent 0 t)))
(clojure-ts--symbol-matches-p
clojure-ts--definition-symbol-regexp
(regexp-opt clojure-ts-function-docstring-symbols)
first-auncle)))))

(defun clojure-ts--match-def-docstring (node)
Expand All @@ -765,7 +783,7 @@ forms like deftype, defrecord, reify, proxy, etc."
(treesit-node-child parent 3 t)
(let ((first-auncle (treesit-node-child parent 0 t)))
(clojure-ts--symbol-matches-p
clojure-ts--variable-definition-symbol-regexp
(regexp-opt clojure-ts-definition-docstring-symbols)
first-auncle)))))

(defun clojure-ts--match-method-docstring (node)
Expand Down
67 changes: 67 additions & 0 deletions test/docstrings.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
(ns clojure-ts-mode.docstrings
"This is a namespace
See my famous `fix-bug` macro if you need help."
(:require [clojure.test :refer [deftest]])
(:import (java.util UUID)))

(def foo ;;asdf
"I'm a value")
(def bar "I'm a docstring" "and I'm a value")

(defonce ^{:doc "gotta document in metadata."} baz
"Did you know defonce doesn't have a docstring arity like def?")

(def foobar
;; Comments shouldn't disrupt docstring highlighting
"I'm a docstring"
123)

(defn ;;asdf
foobarbaz ;;asdf
"I'm the docstring!" ;;asdf
[x]
(inc x))

(;; starting comments break docstrings
defn busted!
"We really need to anchor symbols like defn to the front of the list.
I don't want every query to have to check for comments.
Don't format code this way."
[]
nil)

(defn buzz "Looking for `fizz`"
[x]
(when (zero? (% x 5))
"buzz"))

(defn- fizz
"Pairs well with `buzz`"
[x]
(when (zero? (% x 3))
"fizz"))

(defmacro fix-bug
"Fixes most known bugs."
[& body]
`(try
~@body
(catch Throwable _
nil)))

(definline never-used-this ":)" [x] x)

(deftype ^{:doc "asdf" :something-else "asdf"} T
java.lang.Closeable
(close [this]
(print "done")))

(defprotocol Fooable
(foo [this]
"Does foo"))

(definterface Barable
(^String bar [] "Does bar"))

(deftest ^{:doc "doctest"} some-test
(is (= 1 2)))

0 comments on commit ec48877

Please sign in to comment.