From a4d99be23518a176733ccf81af498bd5a5accc82 Mon Sep 17 00:00:00 2001 From: XeroOl Date: Thu, 10 Apr 2025 14:24:49 -0500 Subject: [PATCH] macro modules and regular modules in separate namespaces implements #49 --- src/fennel-ls/analyzer.fnl | 2 +- src/fennel-ls/compiler.fnl | 1 + src/fennel-ls/config.fnl | 1 + src/fennel-ls/files.fnl | 28 +++++++++++++++------------- src/fennel-ls/lint.fnl | 1 + src/fennel-ls/searcher.fnl | 6 +++--- test/goto-definition.fnl | 34 +++++++++++++++++++++++----------- 7 files changed, 45 insertions(+), 28 deletions(-) diff --git a/src/fennel-ls/analyzer.fnl b/src/fennel-ls/analyzer.fnl index d14e2cb..eb14db7 100644 --- a/src/fennel-ls/analyzer.fnl +++ b/src/fennel-ls/analyzer.fnl @@ -127,7 +127,7 @@ find the definition `10`, but if `opts.stop-early?` is set, it would find (let [mod (. call 2)] (if (= multival 1) (when (= :string (type mod)) - (let [newfile (files.get-by-module server mod)] + (let [newfile (files.get-by-module server mod file.macro-file?)] (when newfile (let [newitem (. newfile.ast (length newfile.ast))] (when (= (length stack) 1) diff --git a/src/fennel-ls/compiler.fnl b/src/fennel-ls/compiler.fnl index 8aba772..135a34c 100644 --- a/src/fennel-ls/compiler.fnl +++ b/src/fennel-ls/compiler.fnl @@ -434,6 +434,7 @@ identifiers are declared / referenced in which places." (cmd)) ;; TODO make this construct an object instead of mutating the file + (set file.macro-file? macro-file?) (set file.ast ast) (set file.calls calls) (set file.lexical lexical) diff --git a/src/fennel-ls/config.fnl b/src/fennel-ls/config.fnl index 362169b..a41573f 100644 --- a/src/fennel-ls/config.fnl +++ b/src/fennel-ls/config.fnl @@ -93,6 +93,7 @@ However, when not an option, fennel-ls will fall back to positionEncoding=\"utf- (λ initialize [server params] (set server.files {}) (set server.modules {}) + (set server.macro-modules {}) (set server.root-uri params.rootUri) (set server.position-encoding (choose-position-encoding params)) (reload server) diff --git a/src/fennel-ls/files.fnl b/src/fennel-ls/files.fnl index 4da8f54..67cd0a1 100644 --- a/src/fennel-ls/files.fnl +++ b/src/fennel-ls/files.fnl @@ -11,6 +11,7 @@ in the \"server\" object." (local {: compile} (require :fennel-ls.compiler)) (λ read-file [server uri] + ;; preload is here so that tests can inject files (case (?. server.preload uri) preload {: uri :text preload} _ (case uri @@ -29,20 +30,21 @@ in the \"server\" object." (tset server.files uri file) file)))) -(λ get-by-module [server module] - ;; check the cache - (case (. server.modules module) - uri - (or (get-by-uri server uri) - ;; if the cached uri isn't found, clear the cache and try again - (do (tset server.modules module nil) - (get-by-module server module))) - nil - (case (searcher.lookup server module) +(λ get-by-module [server module macro?] + (let [modules (if macro? server.macro-modules server.modules)] + ;; check the cache + (case (. modules module) uri - (do - (tset server.modules module uri) - (get-by-uri server uri))))) + (or (get-by-uri server uri) + ;; if the cached uri isn't found, clear the cache and try again + (do (tset modules module nil) + (get-by-module server module))) + nil + (case (searcher.lookup server module macro?) + uri + (do + (tset modules module uri) + (get-by-uri server uri)))))) (λ set-uri-contents [server uri text] (case (. server.files uri) diff --git a/src/fennel-ls/lint.fnl b/src/fennel-ls/lint.fnl index 8ba64e2..7a57dd9 100644 --- a/src/fennel-ls/lint.fnl +++ b/src/fennel-ls/lint.fnl @@ -39,6 +39,7 @@ the `file.diagnostics` field, filling it with diagnostics." ;; this is way too specific; it's also safe to do this inside an `if` or `case` (fn in-or? [calls symbol] + "Check if the symbol is in an expression like `(or unpack table.unpack)`, where we want to suppress the module field stuff." (accumulate [in? false call (pairs calls) &until in?] (and (sym? (. call 1) :or) (utils.find call symbol)))) diff --git a/src/fennel-ls/searcher.fnl b/src/fennel-ls/searcher.fnl index 9a70918..1e6efde 100644 --- a/src/fennel-ls/searcher.fnl +++ b/src/fennel-ls/searcher.fnl @@ -24,13 +24,13 @@ I suspect this file may be gone after a bit of refactoring." (case (io.open (uri->path uri)) f (do (f:close) true)))) -(λ lookup [{:configuration {: fennel-path} :root-uri ?root-uri &as server} mod] - "Use the fennel path to find a file on disk" +(λ lookup [{:configuration {: fennel-path : macro-path} :root-uri ?root-uri &as server} mod macro?] + "Use the configured path to find a file on disk" (when ?root-uri (let [mod (mod:gsub "%." path-sep) root-path (uri->path ?root-uri)] (accumulate [uri nil - segment (fennel-path:gmatch "[^;]+") + segment (: (if macro? macro-path fennel-path) :gmatch "[^;]+") &until uri] (let [segment (segment:gsub "%?" mod) segment (if (absolute-path? segment) diff --git a/test/goto-definition.fnl b/test/goto-definition.fnl index 8e040cb..6541d5a 100644 --- a/test/goto-definition.fnl +++ b/test/goto-definition.fnl @@ -3,8 +3,8 @@ (local {: null} (require :dkjson)) (local {: view} (require :fennel)) -(fn check [file-contents] - (let [{: client : uri : cursor :locations [location]} (create-client file-contents) +(fn check [file-contents ?config] + (let [{: client : uri : cursor :locations [location]} (create-client file-contents nil ?config) [message] (client:definition uri cursor)] (if location (faith.= location message.result @@ -190,15 +190,6 @@ nil) -; (fn test-macro [] -; (check "(macro ==my-macro== [] `nil) -; (my-mac|ro)") -; (check {:m.fnl ";; fennel-ls: macro-file -; (fn ==my-macro== [] `nil) -; {: my-macro}" -; :main.fnl "(import-macros m :m) -; (m.my-macro|)"})) - (fn test-no-crash [] (check "(macro cool [a b] `(let [,b 10] ,a))\n(cool |x ==x==)") (check "(macro cool [a b] `(let [,b 10] ,a))\n(cool x x|)") @@ -217,6 +208,7 @@ (check "(fn foo [] [{} =={}== {}]) (local [x y| z] (foo))") nil) + (fn test-macro [] "Macros are mostly unsupported for now. Fennel-ls can understand the expansion of macros, because it mostly operates @@ -224,12 +216,32 @@ ;; Can see the expansion of a macro (check "(macro hello [x y z] y) (local x| (hello {} =={}== {}))") + ;; ;; Can see the macro itself ;; (check "==(macro hello [x y z] y)== ;; (local x (hello| {} {} {})") + ;; ;; Can see macros thru import-macros + ;; (check {:m.mfnl "(fn ==my-macro== [] `nil) + ;; {: my-macro}" + ;; :main.fnl "(import-macros m :m) + ;; (m.my-macro|)") + + ;; go-to-definition inside of macro files uses the macro path + (check {:abcde/friend.fnl "=={}==" + :friend.fnl "{}" + :main.fnl ";; fennel-ls: macro-file + (local x| (require :friend))"} + {:macro-path "abcde/?.fnl"}) + + (check {:abcde/friend.fnl "{}" + :friend.fnl "=={}==" + :main.fnl "(local x| (require :friend))"} + {:macro-path "abcde/?.fnl"}) + nil) + ; ;; (it "can go to a destructured function argument") ; ;; (it "will give up instead of freezing on recursive requires") ; ;; (it "will give up instead of freezing on recursive tables constructed with (set)")