From 28e20f74cb8f1c83db0f347d58efb69cf2fa7304 Mon Sep 17 00:00:00 2001 From: XeroOl Date: Tue, 8 Jul 2025 21:59:53 -0500 Subject: [PATCH] better completions in destructure bindings --- src/fennel-ls/compiler.fnl | 11 +++- src/fennel-ls/completion.fnl | 109 ++++++++++++++++++++--------------- src/fennel-ls/formatter.fnl | 3 +- test/completion.fnl | 15 ++++- 4 files changed, 87 insertions(+), 51 deletions(-) diff --git a/src/fennel-ls/compiler.fnl b/src/fennel-ls/compiler.fnl index 5512665..8cb2bd5 100644 --- a/src/fennel-ls/compiler.fnl +++ b/src/fennel-ls/compiler.fnl @@ -293,7 +293,16 @@ identifiers are declared / referenced in which places." (let [old (tostring ?ast)] (tset ?ast 1 "!!invalid-multi-symbol!!") (table.insert defer #(tset ?ast 1 old)) - true)))) + true)) + (when (and (= 1 (msg:find "expected name and value")) + (list? ?ast)) + (when (= 1 (length ?ast)) + (table.insert ?ast (sym "_")) + (table.insert defer #(table.remove ?ast))) + (when (= 2 (length ?ast)) + (table.insert ?ast nil*) + (table.insert defer #(table.remove ?ast))) + (= 3 (length ?ast))))) (λ on-compile-error [_ msg ast call-me-to-reset-the-compiler] (let [range (or (message.ast->range server file ast) diff --git a/src/fennel-ls/completion.fnl b/src/fennel-ls/completion.fnl index b87b941..c45dc36 100644 --- a/src/fennel-ls/completion.fnl +++ b/src/fennel-ls/completion.fnl @@ -40,7 +40,7 @@ "add the completion. also recursively adds the fields' completions" (when (not (. seen definition)) (set (. seen definition) true) - (add-completion! name definition) + (add-completion! name definition "Value") (each [field def ?string-method (navigate.iter-fields server definition)] (if (or (= :self (tostring (?. def :metadata :fnl/arglist 1))) ?string-method @@ -56,62 +56,77 @@ (add-completion-recursively! (.. name "." field) def))) (set (. seen definition) false))) - (each [name documentation (pairs hardcoded-completions)] - (add-completion! name documentation)) + (fn expression-completions [] + (each [name documentation (pairs hardcoded-completions)] + (add-completion! name documentation)) - (local seen-manglings {}) + (local seen-manglings {}) - (each [_ global* (ipairs file.allowed-globals)] - (when (not (. seen-manglings global*)) - (set (. seen-manglings global*) true) - (case (analyzer.search-name-and-scope server file global* scope) - def (if (and (= :_G (tostring global*)) - (not (: (tostring ?symbol) :match "_G[:.]"))) - (add-completion! global* def) - (add-completion-recursively! global* def)) - _ (do - (io.stderr:write "BAD!!!! undocumented global: " (tostring global*) "\n") - (add-completion! global* {}))))) + (each [_ global* (ipairs file.allowed-globals)] + (when (not (. seen-manglings global*)) + (set (. seen-manglings global*) true) + (case (analyzer.search-name-and-scope server file global* scope) + def (if (and (= :_G (tostring global*)) + (not (: (tostring ?symbol) :match "_G[:.]"))) + (add-completion! global* def) + (add-completion-recursively! global* def)) + _ (do + (io.stderr:write "BAD!!!! undocumented global: " (tostring global*) "\n") + (add-completion! global* {}))))) + + (var scope scope) + (while scope + (each [mangling (pairs scope.manglings)] + (when (not (. seen-manglings mangling)) + (set (. seen-manglings mangling) true) + (case (analyzer.search-name-and-scope server file mangling scope) + def (add-completion-recursively! mangling def) + _ (add-completion-recursively! mangling {})))) + + (when in-call-position? + (each [macro* macro-value (pairs scope.macros)] + (add-completion! macro* + {:binding macro* + :metadata (. METADATA macro-value)} + :Keyword)) + + (each [special (pairs scope.specials)] + (case (analyzer.search-name-and-scope server file special scope) + def (add-completion! special def :Operator) + _ (do + (io.stderr:write "BAD!!!! undocumented special: " (tostring special) "\n") + {:label special})))) + (set scope scope.parent))) + + (fn binding-completions [] + "completions when you're writing a destructure pattern. We suggest identifiers which are unknown" + (each [_ {: message} (ipairs file.diagnostics)] + (case (message:match "unknown identifier: ([a-zA-Z0-9_-]+)") + identifier (add-completion! identifier {} :Variable)))) + + (if (. file.definitions ?symbol) + (binding-completions) + (expression-completions)) - (var scope scope) - (while scope - (each [mangling (pairs scope.manglings)] - (when (not (. seen-manglings mangling)) - (set (. seen-manglings mangling) true) - (case (analyzer.search-name-and-scope server file mangling scope) - def (add-completion-recursively! mangling def) - _ (add-completion-recursively! mangling {})))) - - (when in-call-position? - (each [macro* macro-value (pairs scope.macros)] - (add-completion! macro* - {:binding macro* - :metadata (. METADATA macro-value)} - :Keyword)) - - (each [special (pairs scope.specials)] - (case (analyzer.search-name-and-scope server file special scope) - def (add-completion! special def :Operator) - _ (do - (io.stderr:write "BAD!!!! undocumented special: " (tostring special) "\n") - {:label special})))) - (set scope scope.parent)) (if server.can-do-good-completions? {:itemDefaults {:editRange range :data {: uri : byte}} :items results} results))) (fn completionItem/resolve [server _send completion-item] - (or (. hardcoded-completions completion-item.label) - (let [{: uri : byte} completion-item.data - file (files.get-by-uri server uri) - (_symbol parents) (analyzer.find-symbol file.ast byte) - scope (or (accumulate [?find nil _ parent (ipairs parents) &until ?find] - (. file.scopes parent)) - file.scope)] - (case (analyzer.search-name-and-scope server file completion-item.label scope) - result (doto completion-item (tset :documentation (format.hover-format server completion-item.label result))))))) + (let [result + (or (. hardcoded-completions completion-item.name) + (let [{: uri : byte} completion-item.data + file (files.get-by-uri server uri) + (_symbol parents) (analyzer.find-symbol file.ast byte) + scope (or (accumulate [?find nil _ parent (ipairs parents) &until ?find] + (. file.scopes parent)) + file.scope)] + (analyzer.search-name-and-scope server file completion-item.label scope)))] + (when result + (set completion-item.documentation (format.hover-format server completion-item.label result))) + completion-item)) {: textDocument/completion : completionItem/resolve} diff --git a/src/fennel-ls/formatter.fnl b/src/fennel-ls/formatter.fnl index 79469ac..58a439e 100644 --- a/src/fennel-ls/formatter.fnl +++ b/src/fennel-ls/formatter.fnl @@ -193,12 +193,13 @@ fntype is one of fn or λ or lambda" {:label name :documentation (when (not server.can-do-good-completions?) (hover-format server name definition)) :textEdit (when (not server.can-do-good-completions?) {:newText name : range}) - :kind (or (?. kinds ?kind) + :kind (or (if (not= ?kind :Value) (?. kinds ?kind)) (case (navigate.getmetadata server definition) metadata (or (?. kinds metadata.fls/itemKind) (when metadata.fnl/arglist (if (name:find ":") kinds.Method kinds.Function)))) + (?. kinds ?kind) kinds.Text)}) {: signature-help-format diff --git a/test/completion.fnl b/test/completion.fnl index aebe81f..d313a27 100644 --- a/test/completion.fnl +++ b/test/completion.fnl @@ -164,7 +164,8 @@ (check "(local x {:field (fn [self])})\n(x::f" [] []) (check "(let [my-table {:foo 10 :bar 20}]\n my-table.|)))" - [:my-table.foo :my-table.bar] + [{:label :my-table.foo :kind kinds.Value} + {:label :my-table.bar :kind kinds.Value}] []) (check {:main.fnl "(let [foo (require :fooo)] @@ -256,6 +257,15 @@ []) nil) +(fn test-destructure [] + ;; this is a binding variable, we don't want all the normal completions + (check "(local f|)\n(print foo)" + [:foo] + [:setmetatable :_G]) + (check "(let [f|]\n(print foo)" + [:foo] + [:setmetatable :_G]) + nil) ;; ;; Future tests / features ;; ;; Scope Ordering Rules @@ -283,4 +293,5 @@ : test-fn-arg : test-field : test-docs - : test-module} + : test-module + : test-destructure}