diff --git a/src/fennel-ls/formatter.fnl b/src/fennel-ls/formatter.fnl index f52055b..2297796 100644 --- a/src/fennel-ls/formatter.fnl +++ b/src/fennel-ls/formatter.fnl @@ -20,6 +20,14 @@ user code." " ...)")) (if docstring (.. "\n" docstring) ""))) +(fn metadata-format [{: binding : metadata}] + "formats a special using its builtin metadata magic" + (.. + (code-block + (.. "(" (tostring binding) " " (table.concat metadata.fnl/arglist " ") ")")) + "\n" + metadata.fnl/docstring)) + (λ fn? [symbol] (if (sym? symbol) (let [name (tostring symbol)] @@ -27,14 +35,14 @@ user code." (= name "λ") (= name "lambda"))))) -(λ analyze-fn [ast] +(λ analyze-fn [?ast] "if ast is a function definition, try to fill out as much of this as possible: {: name : arglist : docstring : fntype} fntype is one of fn or λ or lambda" - (case ast + (case ?ast ;; name + docstring (where [fntype name arglist docstring _body] (fn? fntype) @@ -66,13 +74,16 @@ fntype is one of fn or λ or lambda" :value (case (analyze-fn result.definition) {:fntype ?fntype :name ?name :arglist ?arglist :docstring ?docstring} (fn-format ?fntype ?name ?arglist ?docstring) - _ (code-block - (if (-?>> result.keys length (< 0)) + _ (if (-?>> result.keys length (< 0)) + (code-block (.. "ERROR, I don't know how to show this " "(. " (view result.definition {:prefer-colon? true}) " " - (view result.keys {:prefer-colon? true}) ")") - (view result.definition {:prefer-colon? true}))))}) + (view result.keys {:prefer-colon? true}) ")")) + result.metadata + (metadata-format result) + (code-block + (view result.definition {:prefer-colon? true}))))}) ;; CompletionItemKind (local kinds diff --git a/src/fennel-ls/handlers.fnl b/src/fennel-ls/handlers.fnl index 8459c6d..2e06641 100644 --- a/src/fennel-ls/handlers.fnl +++ b/src/fennel-ls/handlers.fnl @@ -140,7 +140,7 @@ Every time the client sends a message, it gets handled by a function in the corr (λ scope-completion [self file byte ?symbol parents] (let [scope (or (accumulate [result nil - i parent (ipairs parents) + _ parent (ipairs parents) &until result] (. file.scopes parent)) file.scope) @@ -150,7 +150,7 @@ Every time the client sends a message, it gets handled by a function in the corr (collect-scope scope :manglings #(make-completion-item self file $ scope) result) (when in-call-position? (collect-scope scope :macros #{:label $ :kind kinds.Keyword} result) - (collect-scope scope :specials #{:label $ :kind kinds.Keyword} result)) + (collect-scope scope :specials #(make-completion-item self file $ scope) result)) (icollect [_ k (ipairs file.allowed-globals) &into result] {:label k :kind kinds.Variable}))) @@ -162,9 +162,9 @@ Every time the client sends a message, it gets handled by a function in the corr (case (language.search-assignment self file ref stack {}) {: definition} (case (values definition (type definition)) - (_str :string) (icollect [k v (pairs string)] + (_str :string) (icollect [k _ (pairs string)] {:label k :kind kinds.Field}) - (tbl :table) (icollect [k v (pairs tbl)] + (tbl :table) (icollect [k _ (pairs tbl)] (if (= (type k) :string) {:label k :kind kinds.Field}))) _ nil)))) diff --git a/src/fennel-ls/language.fnl b/src/fennel-ls/language.fnl index c33268e..771e507 100644 --- a/src/fennel-ls/language.fnl +++ b/src/fennel-ls/language.fnl @@ -90,6 +90,10 @@ the data provided by compiler.fnl." (= 0 (length stack)) {:definition item} ;; BASE CASE !! (error (.. "I don't know what to do with " (view item)))))) +(local {:metadata METADATA + :scopes {:global {:specials SPECIALS}}} + (require :fennel.compiler)) + (λ search-main [self file symbol opts ?byte] "Find the definition of a symbol @@ -125,11 +129,8 @@ Returns: (if (sym? symbol) (let [split (utils.multi-sym-split symbol (if ?byte (+ 1 (- ?byte symbol.bytestart)))) stack (stack-add-split! [] split)] - (local {:metadata METADATA - :scopes {:global {:specials SPECIALS}}} - (require :fennel.compiler)) (case (. METADATA (. SPECIALS (tostring symbol))) - metadata {: metadata} + metadata {:binding symbol : metadata} _ (case (. file.references symbol) ref (search-assignment self file ref stack opts) _ (case (. file.definitions symbol) @@ -141,9 +142,13 @@ Returns: (find-local-definition file name ?scope.parent)))) (λ search-name-and-scope [self file name scope ?opts] + "find a definition just from the name of the item, and the scope it is in" + (assert (= (type name) :string)) (let [stack (stack-add-multisym! [] name)] - (case (find-local-definition file name scope) - def (search self file def.definition (stack-add-keys! stack def.keys) (or ?opts {}))))) + (case (. METADATA (. SPECIALS name)) + metadata {:binding (sym name) : metadata} + _ (case (find-local-definition file name scope) + def (search self file def.definition (stack-add-keys! stack def.keys) (or ?opts {})))))) (λ past? [?ast byte] ;; check if a byte is past an ast object diff --git a/test/completion-test.fnl b/test/completion-test.fnl index 42292d5..ccc9db7 100644 --- a/test/completion-test.fnl +++ b/test/completion-test.fnl @@ -119,6 +119,25 @@ (assert completion.kind "completion kind should be present") (assert completion.documentation "completion documentation should be present"))))) + ; (it "offers rich information about global completions" + ; (let [client (doto (create-client) + ; (: :open-file! filename "(")) + ; [{:result completions}] (client:completion filename 0 1) + ; _ (table.sort completions #(< $1.label $2.label)) + ; count (accumulate [sum 0 _ completion (ipairs completions)] + ; (if (and (= (type completion.label) :string) + ; (= (type completion.kind) :number) + ; (= (type completion.documentation) :table)) + ; (+ sum 1) + ; sum))] + ; (if (< count (length completions)) + ; (print "!!!" (- (length completions) count) "items are missing a label or kind or doc")) + ; (each [_ completion (ipairs completions)] + ; (is.same (type completion.label) :string "unlabeled completion") + ; (is.same (type completion.kind) :number (.. completion.label " needs a kind")) + ; (is.same (type completion.documentation) :table (.. completion.label " needs documentation"))))))) + + ;; (it "offers rich information about macro completions") ;; (it "offers rich information about variable completions") ;; (it "offers rich information about field completions") diff --git a/test/hover-test.fnl b/test/hover-test.fnl index a1d9eaf..192bbbf 100644 --- a/test/hover-test.fnl +++ b/test/hover-test.fnl @@ -49,4 +49,7 @@ (is-matching message [{:jsonrpc "2.0" :id 2}] ""))) (it "can go backward through (case)" - (check "hover.fnl" 22 22 "```fnl\n{:AB :CD}\n```"))) + (check "hover.fnl" 22 22 "```fnl\n{:AB :CD}\n```")) + + (it "hovers over a special" + (check "hover.fnl" 5 2 "```fnl\n(let [name1 val1 ... nameN valN] ...)\n```\nIntroduces a new scope in which a given set of local bindings are used."))) diff --git a/test/is.fnl b/test/is.fnl index b95d1bb..75ea419 100644 --- a/test/is.fnl +++ b/test/is.fnl @@ -2,8 +2,8 @@ (local {: view} (require :fennel)) (local {: expect} (require :test.lust)) ;; lust uses weird terminology, but equal is by __eq, same is by recursively having the same contents -(setmetatable {:equal #(do ((. (expect $1) :to :be) $2) true) - :same #(do ((. (expect $1) :to :equal) $2) true) +(setmetatable {:equal #(do ((. (expect $1) :to :be) $2) true) + :same #(do ((. (expect $1) :to :equal) $2 $3) true) :nil #(do ((. (expect $1) :to_not :exist)) true) :not {:nil #(do ((. (expect $1) :to :exist)) true)} :truthy #(do ((. (expect $1) :to :be :truthy)) true)} diff --git a/test/lust.lua b/test/lust.lua index b522f6a..73216c3 100644 --- a/test/lust.lua +++ b/test/lust.lua @@ -147,10 +147,10 @@ local paths = { end }, equal = { - test = function(v, x) + test = function(v, x, message) return strict_eq(v, x), - 'expected ' .. tostring(v) .. ' and ' .. tostring(x) .. ' to be exactly equal', - 'expected ' .. tostring(v) .. ' and ' .. tostring(x) .. ' to not be exactly equal' + message or 'expected ' .. tostring(v) .. ' and ' .. tostring(x) .. ' to be exactly equal', + message or 'expected ' .. tostring(v) .. ' and ' .. tostring(x) .. ' to not be exactly equal' end }, have = {