aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorOscar Najera <hi@oscarnajera.com>2024-08-19 00:35:16 +0200
committerOscar Najera <hi@oscarnajera.com>2024-08-19 00:35:16 +0200
commit6fcff0d748eef1998cec47470f10f64c66d5aa99 (patch)
tree815e9c66799008d6beb01cc54e05932efecb210a
parent42ce72ded8a074654c3dab55ac33b0b932c597df (diff)
downloaddotfiles-6fcff0d748eef1998cec47470f10f64c66d5aa99.tar.gz
dotfiles-6fcff0d748eef1998cec47470f10f64c66d5aa99.tar.bz2
dotfiles-6fcff0d748eef1998cec47470f10f64c66d5aa99.zip
YAML mode tree sitter imenu
-rw-r--r--config/doom/config.org72
1 files changed, 72 insertions, 0 deletions
diff --git a/config/doom/config.org b/config/doom/config.org
index bec9fc0..6a2df58 100644
--- a/config/doom/config.org
+++ b/config/doom/config.org
@@ -604,6 +604,78 @@ Emacs mode for managing ledger text files
:load-path "~/dev/emacs-lisp/emacs-libyaml/")
(use-package! yaml)
+
+(after! yaml-mode
+ ;; Inspired by this blog post to use tree sitter to create a imenu list.
+ ;; https://blog.meain.io/2022/navigating-config-files-using-tree-sitter/
+ (defun on/ts-elements ()
+ "Tree sitter matches for all entries in data buffer."
+ (let ((query (thread-last
+ (pcase major-mode
+ ('json-mode "(object (pair (string (string_content) @key) (_)) @item)")
+ ('yaml-mode "(block_mapping_pair (flow_node) @key (_)) @item"))
+ (tsc-make-query tree-sitter-language)))
+ (root-node (tsc-root-node tree-sitter-tree)))
+ (tsc-query-matches query root-node #'tsc--buffer-substring-no-properties)))
+
+ (defun on/pos-items (ts-match)
+ "From a TS-MATCH get element key name and region in buffer."
+ (pcase-let ((`[(_ . ,item) (_ . ,key)] (cdr ts-match)))
+ (list
+ (tsc-node-text key)
+ (tsc-node-byte-range item)
+ ;; (destructuring-bind (s . e) (tsc-node-byte-range key)
+ ;; (cons (byte-to-position s) (byte-to-position e)))
+ )))
+
+ (defun on/nested-item-names (items)
+ "From named ITEMS figure out the nested hierarchy for names."
+ (let ((node-stack '(("" 0)))
+ result)
+ (dolist (item items (nreverse result))
+ (pcase-let ((`(,item-name (,item-start . ,item-end)) item)
+ (`((_ ,parent-end)) node-stack))
+ (when (< parent-end item-end)
+ (setq node-stack (seq-filter (lambda (y)
+ (<= item-end (cadr y)))
+ node-stack)))
+ (push (list item-name item-end) node-stack)
+ (push (list (nreverse (mapcar #'car node-stack))
+ item-start)
+ result)))))
+
+ (defun on/imenu-nested-path-elements ()
+ "On a yaml or json buffer get the element positions for imenu."
+ (thread-last
+ (seq-map #'on/pos-items (on/ts-elements))
+ (on/nested-item-names)
+ (seq-map (lambda (item)
+ (seq-let (name-chain start) item
+ (cons (string-join name-chain ".") (byte-to-position start)))))))
+
+ (remove-hook 'yaml-mode-hook 'yaml-set-imenu-generic-expression) ;; don't use default one
+ (add-hook 'yaml-mode-hook
+ (lambda ()
+ (setq imenu-create-index-function #'on/imenu-nested-path-elements)))
+
+ (ert-deftest on/tree-sitter-imenu ()
+ "Test the imenu generation."
+ (with-temp-buffer
+ (yaml-mode)
+ (tree-sitter-mode)
+ (insert "one: Ñaño
+skills:
+ run: 3
+ jump:
+ high: 7
+ low: 2
+
+")
+ (should
+ (equal '(("one" . 1) ("skills" . 13) ("skills.run" . 23) ("skills.jump" . 32) ("skills.jump.high" . 43) ("skills.jump.low" . 55))
+ (on/imenu-nested-path-elements)))
+ ))
+ )
#+end_src
** Databases