This afternoon, I was exploring some Tree Sitter 📖 functions in Emacs 📖. I wanted to take a Ruby 📖’s method’s signature and create Yardoc stubs from the method parameters.
Requirements
Let’s say I have the following Ruby method definition:
def call(foo, bar:, baz: :default, **kwargs)
# do stuff
end
I wanted to call a function and update the buffer as follows:
##
# @param foo [Object]
# @param bar [Object]
# @param baz [Object]
# @param kwargs [Object]
def call(foo, bar:, baz: :default, **kwargs)
# do stuff
end
update
I received an email pointing out that I had mixed the treesit
(e.g. treesit-inspect-node-at-point
) and tree-sitter
(e.g. tsc-get-child-by-field
) functions.
Gah! I had that in my kill-ring
. I also tried the following to no avail:
(let ((func-node (tree-sitter-node-at-point 'method))
(params (tsc-get-child-by-field func-node ':method_parameters)))
(message "%s" params))
The email also pointed out that my “Reply by Email” link was broken; so I fixed that.
Thank you dear reader!
The Interlude and Solution
I was encountering problems with tree-sitter
functionality.
The tree-sitter
package is an external package. The treesit
is a built-in package in Emacs 29. I prefer tree-sitter
as it’s more performant in my use case.
The following emacs-lisp
writes a nil
message:
(let ((func-node (treesit-inspect-node-at-point 'method))
(params (tsc-get-child-by-field func-node ':method_parameters)))
(message "%s" params))
The above, in my reading, should’ve found the node that had the method parameters.
Running into those problems, I took a different path. String parsing and regular expressions. Below is that solution:
(defun jf/ruby-mode/yardoc-ify ()
"Add parameter yarddoc stubs for the current method."
(interactive)
;; Remember where we started.
(save-excursion
;; Goto the beginning of the function
(ruby-beginning-of-defun)
;; Move to just after the first (
(search-forward "(")
;; Move back to just before the (
(backward-char)
;; Select parameters declaration
(mark-sexp)
;; Copy that
(copy-region-as-kill (point) (mark))
;; Split apart the parameters into their identifiers
(let ((identifiers (mapcar (lambda (token)
(replace-regexp-in-string
"[^a-z|_]" ""
(car (s-split " "
(s-trim token)))))
(s-split "," (substring-no-properties
(car kill-ring))))))
;; Go to the beginning of the function again
(ruby-beginning-of-defun)
;; Now insert the identifiers as yardoc
(insert "##\n"
(s-join "\n" (mapcar
(lambda (param)
(concat "# @param "
param
" [Object]"))
identifiers))
"\n"))))