From ae8a0f9f3cee4154cc70a6b2dfb63fda647c3b3a Mon Sep 17 00:00:00 2001 From: Jason Shipman <1744045+jship@users.noreply.github.com> Date: Sun, 23 Aug 2026 12:52:07 -0400 Subject: [PATCH] Improve hot reload for conjure.tree-sitter module --- fnl/conjure/tree-sitter.fnl | 98 ++++++++++++++------------------- lua/conjure/tree-sitter.lua | 106 ++++++++++++++++++------------------ 2 files changed, 96 insertions(+), 108 deletions(-) diff --git a/fnl/conjure/tree-sitter.fnl b/fnl/conjure/tree-sitter.fnl index a149fe26..9e89c35a 100644 --- a/fnl/conjure/tree-sitter.fnl +++ b/fnl/conjure/tree-sitter.fnl @@ -1,4 +1,4 @@ -(local {: autoload} (require :conjure.nfnl.module)) +(local {: autoload : define} (require :conjure.nfnl.module)) (local a (autoload :conjure.nfnl.core)) (local client (autoload :conjure.client)) (local config (autoload :conjure.config)) @@ -6,7 +6,9 @@ ;; Initially based on https://github.com/savq/conjure-julia <3 -(fn enabled? [] +(local M (define :conjure.tree-sitter)) + +(fn M.enabled? [] "Do we have tree-sitter support in the current nvim, buffer and filetype. If this is false, you might need to install https://github.com/nvim-treesitter/nvim-treesitter @@ -20,12 +22,12 @@ true false)) -(fn parse! [] +(fn M.parse! [] (let [(ok? parser) (pcall vim.treesitter.get_parser)] (if (and ok? (not= nil parser)) (parser:parse)))) -(fn node->str [node] +(fn M.node->str [node] "Turn the node into a string, nils flow through. Separate forms are joined by new lines." (when node @@ -33,27 +35,27 @@ (vim.treesitter.get_node_text node (vim.api.nvim_get_current_buf)) (vim.treesitter.query.get_node_text node (vim.api.nvim_get_current_buf))))) -(fn lisp-comment-node? [node] +(fn M.lisp-comment-node? [node] "Node is a (comment ...) form" - (text.starts-with (node->str node) "(comment")) + (text.starts-with (M.node->str node) "(comment")) -(fn parent [node] +(fn M.parent [node] "Get the parent if possible." (when node (node:parent))) -(fn document? [node] +(fn M.document? [node] "Is the node the entire document, i.e. has no parent?" - (not (parent node))) + (not (M.parent node))) -(fn range [node] +(fn M.range [node] "Get the character range of the form." (when node (let [(sr sc er ec) (node:range)] {:start [(a.inc sr) sc] :end [(a.inc er) (a.dec ec)]}))) -(fn node->table [node] +(fn M.node->table [node] "If it is a node, convert it to a Lua table we can work with in Conjure. If it's already a table with the right keys just return that." (if @@ -61,25 +63,25 @@ node node - {:range (range node) - :content (node->str node) + {:range (M.range node) + :content (M.node->str node) :node node} nil)) -(fn get-root [node] +(fn M.get-root [node] "Get the root node below the entire document." - (parse!) + (M.parse!) (let [node (or node (vim.treesitter.get_node)) - parent-node (parent node)] + parent-node (M.parent node)] (if - (document? node) nil - (document? parent-node) node + (M.document? node) nil + (M.document? parent-node) node (client.optional-call :comment-node? parent-node) node - (get-root parent-node)))) + (M.get-root parent-node)))) -(fn leaf? [node] +(fn M.leaf? [node] "Does the node have any children? Or is it the end of the tree?" (when node (= 0 (node:child_count)))) @@ -87,26 +89,26 @@ ;; Some node types I've seen: sym_lit, symbol, multi_symbol... ;; So I'm not sure if each language just picks a flavour, but this should cover all of our bases. ;; Clients can also opt in and hint with their own symbol-node? functions now too. -(fn sym? [node] +(fn M.sym? [node] (when node (or (string.find (node:type) :sym) (= (node:type) :package_lit) ;; just for common lisp (vim.tbl_contains [:field_expression :scoped_identifier] (node:type)) ;; just for julia (client.optional-call :symbol-node? node)))) -(fn get-leaf [node] +(fn M.get-leaf [node] "Return the leaf node under the cursor or nothing at all." - (parse!) + (M.parse!) (let [node (or node (vim.treesitter.get_node))] - (when (or (leaf? node) (sym? node)) + (when (or (M.leaf? node) (M.sym? node)) (var node node) - (while (sym? (parent node)) - (set node (parent node))) + (while (M.sym? (M.parent node)) + (set node (M.parent node))) node))) -(fn node-surrounded-by-form-pair-chars? [node extra-pairs] - (let [node-str (node->str node) +(fn M.node-surrounded-by-form-pair-chars? [node extra-pairs] + (let [node-str (M.node->str node) first-and-last-chars (text.first-and-last-chars node-str)] (or (a.some (fn [[start end]] @@ -119,15 +121,15 @@ extra-pairs) false))) -(fn node-prefixed-by-chars? [node prefixes] - (let [node-str (node->str node)] +(fn M.node-prefixed-by-chars? [node prefixes] + (let [node-str (M.node->str node)] (or (a.some (fn [prefix] (vim.startswith node-str prefix)) prefixes) false))) -(fn get-form [node] +(fn M.get-form [node] "Get the current form under the cursor. Walks up until it finds a non-leaf. Warning, this can return a table containing content and range! Use @@ -137,12 +139,12 @@ ;; already called parse! and we shouldn't waste time calling it again, only ;; the first time. (when (not node) - (parse!)) + (M.parse!)) (let [node (or node (vim.treesitter.get_node))] (if ;; If we're already at the root then we're not in a form. - (document? node) + (M.document? node) nil ;; We don't treat leaves as forms. That could be a single paren or quote @@ -154,9 +156,9 @@ ;; node you wish to jump to. This is here for backwards compatibility and ;; simpler use cases. I recommend using get-form-modifier for new use ;; cases. - (or (leaf? node) + (or (M.leaf? node) (= false (client.optional-call :form-node? node))) - (get-form (parent node)) + (M.get-form (M.parent node)) ;; Each client gets to modify the form, this means they can traverse the ;; tree until they find something they're happy with. The client should @@ -171,7 +173,7 @@ ;; Walk upwards by one. (= :parent modifier) - (get-form (parent node)) + (M.get-form (M.parent node)) ;; An actual node! Use that one. (= :node modifier) @@ -190,7 +192,7 @@ (a.println "Warning: Conjure client returned an unknown get-form-modifier" res) node)))))) -(fn add-language [lang] +(fn M.add-language [lang] (let [add (case vim.treesitter {:language {:add f}} f {:language {:require_language f}} (partial pcall f) @@ -206,27 +208,11 @@ (let [root-tree (. trees 1)] (root-tree:root)))))) -(fn valid-str? [lang code] - (if (enabled?) +(fn M.valid-str? [lang code] + (if (M.enabled?) (let [root-node (get-root-node-for-str lang code)] (and root-node (not (root-node:has_error)))) true)) -{: enabled? - : parse! - : node->str - : lisp-comment-node? - : parent - : document? - : range - : node->table - : get-root - : leaf? - : sym? - : get-leaf - : node-surrounded-by-form-pair-chars? - : node-prefixed-by-chars? - : get-form - : add-language - : valid-str?} +M diff --git a/lua/conjure/tree-sitter.lua b/lua/conjure/tree-sitter.lua index 43ab9a18..7595bc2a 100644 --- a/lua/conjure/tree-sitter.lua +++ b/lua/conjure/tree-sitter.lua @@ -1,11 +1,13 @@ -- [nfnl] fnl/conjure/tree-sitter.fnl local _local_1_ = require("conjure.nfnl.module") local autoload = _local_1_.autoload +local define = _local_1_.define local a = autoload("conjure.nfnl.core") local client = autoload("conjure.client") local config = autoload("conjure.config") local text = autoload("conjure.text") -local function enabled_3f() +local M = define("conjure.tree-sitter") +M["enabled?"] = function() local and_2_ = config["get-in"]({"extract", "tree_sitter", "enabled"}) if and_2_ then local ok_3f, parser = pcall(vim.treesitter.get_parser) @@ -17,7 +19,7 @@ local function enabled_3f() return false end end -local function parse_21() +M["parse!"] = function() local ok_3f, parser = pcall(vim.treesitter.get_parser) if (ok_3f and (nil ~= parser)) then return parser:parse() @@ -25,7 +27,7 @@ local function parse_21() return nil end end -local function node__3estr(node) +M["node->str"] = function(node) if node then if vim.treesitter.get_node_text then return vim.treesitter.get_node_text(node, vim.api.nvim_get_current_buf()) @@ -36,20 +38,20 @@ local function node__3estr(node) return nil end end -local function lisp_comment_node_3f(node) - return text["starts-with"](node__3estr(node), "(comment") +M["lisp-comment-node?"] = function(node) + return text["starts-with"](M["node->str"](node), "(comment") end -local function parent(node) +M.parent = function(node) if node then return node:parent() else return nil end end -local function document_3f(node) - return not parent(node) +M["document?"] = function(node) + return not M.parent(node) end -local function range(node) +M.range = function(node) if node then local sr, sc, er, ec = node:range() return {start = {a.inc(sr), sc}, ["end"] = {a.inc(er), a.dec(ec)}} @@ -57,92 +59,92 @@ local function range(node) return nil end end -local function node__3etable(node) +M["node->table"] = function(node) if (a.get(node, "range") and a.get(node, "content")) then return node elseif node then - return {range = range(node), content = node__3estr(node), node = node} + return {range = M.range(node), content = M["node->str"](node), node = node} else return nil end end -local function get_root(node) - parse_21() +M["get-root"] = function(node) + M["parse!"]() local node0 = (node or vim.treesitter.get_node()) - local parent_node = parent(node0) - if document_3f(node0) then + local parent_node = M.parent(node0) + if M["document?"](node0) then return nil - elseif document_3f(parent_node) then + elseif M["document?"](parent_node) then return node0 elseif client["optional-call"]("comment-node?", parent_node) then return node0 else - return get_root(parent_node) + return M["get-root"](parent_node) end end -local function leaf_3f(node) +M["leaf?"] = function(node) if node then return (0 == node:child_count()) else return nil end end -local function sym_3f(node) +M["sym?"] = function(node) if node then return (string.find(node:type(), "sym") or (node:type() == "package_lit") or vim.tbl_contains({"field_expression", "scoped_identifier"}, node:type()) or client["optional-call"]("symbol-node?", node)) else return nil end end -local function get_leaf(node) - parse_21() +M["get-leaf"] = function(node) + M["parse!"]() local node0 = (node or vim.treesitter.get_node()) - if (leaf_3f(node0) or sym_3f(node0)) then + if (M["leaf?"](node0) or M["sym?"](node0)) then local node1 = node0 - while sym_3f(parent(node1)) do - node1 = parent(node1) + while M["sym?"](M.parent(node1)) do + node1 = M.parent(node1) end return node1 else return nil end end -local function node_surrounded_by_form_pair_chars_3f(node, extra_pairs) - local node_str = node__3estr(node) +M["node-surrounded-by-form-pair-chars?"] = function(node, extra_pairs) + local node_str = M["node->str"](node) local first_and_last_chars = text["first-and-last-chars"](node_str) - local function _16_(_15_) - local start = _15_[1] - local _end = _15_[2] + local function fn_16_(arg_15_) + local start = arg_15_[1] + local _end = arg_15_[2] return (first_and_last_chars == (start .. _end)) end - local or_17_ = a.some(_16_, config["get-in"]({"extract", "form_pairs"})) + local or_17_ = a.some(fn_16_, config["get-in"]({"extract", "form_pairs"})) if not or_17_ then - local function _19_(_18_) - local start = _18_[1] - local _end = _18_[2] + local function fn_19_(arg_18_) + local start = arg_18_[1] + local _end = arg_18_[2] return (vim.startswith(node_str, start) and vim.endswith(node_str, _end)) end - or_17_ = a.some(_19_, extra_pairs) + or_17_ = a.some(fn_19_, extra_pairs) end return (or_17_ or false) end -local function node_prefixed_by_chars_3f(node, prefixes) - local node_str = node__3estr(node) - local function _20_(prefix) +M["node-prefixed-by-chars?"] = function(node, prefixes) + local node_str = M["node->str"](node) + local function fn_20_(prefix) return vim.startswith(node_str, prefix) end - return (a.some(_20_, prefixes) or false) + return (a.some(fn_20_, prefixes) or false) end -local function get_form(node) +M["get-form"] = function(node) if not node then - parse_21() + M["parse!"]() else end local node0 = (node or vim.treesitter.get_node()) - if document_3f(node0) then + if M["document?"](node0) then return nil - elseif (leaf_3f(node0) or (false == client["optional-call"]("form-node?", node0))) then - return get_form(parent(node0)) + elseif (M["leaf?"](node0) or (false == client["optional-call"]("form-node?", node0))) then + return M["get-form"](M.parent(node0)) else local _let_22_ = (client["optional-call"]("get-form-modifier", node0) or {}) local modifier = _let_22_.modifier @@ -150,7 +152,7 @@ local function get_form(node) if (not modifier or ("none" == modifier)) then return node0 elseif ("parent" == modifier) then - return get_form(parent(node0)) + return M["get-form"](M.parent(node0)) elseif ("node" == modifier) then return res.node elseif ("raw" == modifier) then @@ -161,7 +163,7 @@ local function get_form(node) end end end -local function add_language(lang) +M["add-language"] = function(lang) local add do local case_25_ = vim.treesitter @@ -170,16 +172,16 @@ local function add_language(lang) add = f elseif ((_G.type(case_25_) == "table") and ((_G.type(case_25_.language) == "table") and (nil ~= case_25_.language.require_language))) then local f = case_25_.language.require_language - local function _26_(...) + local function fn_26_(...) return pcall(f, ...) end - add = _26_ + add = fn_26_ elseif ((_G.type(case_25_) == "table") and (nil ~= case_25_.require_language)) then local f = case_25_.require_language - local function _27_(...) + local function fn_27_(...) return pcall(f, ...) end - add = _27_ + add = fn_27_ else add = nil end @@ -197,12 +199,12 @@ local function get_root_node_for_str(lang, code) return nil end end -local function valid_str_3f(lang, code) - if enabled_3f() then +M["valid-str?"] = function(lang, code) + if M["enabled?"]() then local root_node = get_root_node_for_str(lang, code) return (root_node and not root_node:has_error()) else return true end end -return {["enabled?"] = enabled_3f, ["parse!"] = parse_21, ["node->str"] = node__3estr, ["lisp-comment-node?"] = lisp_comment_node_3f, parent = parent, ["document?"] = document_3f, range = range, ["node->table"] = node__3etable, ["get-root"] = get_root, ["leaf?"] = leaf_3f, ["sym?"] = sym_3f, ["get-leaf"] = get_leaf, ["node-surrounded-by-form-pair-chars?"] = node_surrounded_by_form_pair_chars_3f, ["node-prefixed-by-chars?"] = node_prefixed_by_chars_3f, ["get-form"] = get_form, ["add-language"] = add_language, ["valid-str?"] = valid_str_3f} +return M