From 8d74f0134a75b0ff25dd268cab303d88fc97d5ec Mon Sep 17 00:00:00 2001 From: Michele Campeotto Date: Fri, 21 Mar 2025 17:14:08 +0100 Subject: [PATCH] Format function signatures from ast and metadata the same. Function signatures rendered from ast were being rendered differently from signatures rendered from metadata, this unifies them, using the metadata format for both. Before: (fn func-name [arg1 arg2] ...) After: (func-name arg1 arg2) This signature is used in the 'hover' feature and in completion documentation. This patch also adds three dashes as a separator between the signature and the documentation text to improve readability. Since the text is being interpreted as Markdown, this results in a line being drawn. This format convention matches other language servers, for example LuaLS. --- src/fennel-ls/analyzer.fnl | 5 ++--- src/fennel-ls/formatter.fnl | 35 ++++++++++++++--------------------- src/fennel-ls/handlers.fnl | 5 +++-- test/hover.fnl | 31 +++++++++++++++++-------------- 4 files changed, 36 insertions(+), 40 deletions(-) diff --git a/src/fennel-ls/analyzer.fnl b/src/fennel-ls/analyzer.fnl index b048359..89fabac 100644 --- a/src/fennel-ls/analyzer.fnl +++ b/src/fennel-ls/analyzer.fnl @@ -274,12 +274,11 @@ find the definition `10`, but if `opts.stop-early?` is set, it would find (fcollect [i 1 (length parents)] (. parents (- (length parents) i -1))))) -(λ find-nearest-call [server file position] +(λ find-nearest-call [_server file byte] "Find the nearest call returns the called symbol and the argument number position points to" - (let [byte (utils.position->byte file.text position server.position-encoding) - (_ [[call] [parent]]) (find-symbol file.ast byte)] + (let [(_ [[call] [parent]]) (find-symbol file.ast byte)] (if (special? parent) (values parent -1) (values call -1)))) diff --git a/src/fennel-ls/formatter.fnl b/src/fennel-ls/formatter.fnl index 0eb2b96..9f82860 100644 --- a/src/fennel-ls/formatter.fnl +++ b/src/fennel-ls/formatter.fnl @@ -11,34 +11,27 @@ user code. Fennel-ls doesn't support user-code formatting as of now." (λ render-arg [arg] (case (type arg) - :table (view arg {:one-line? true - :prefer-colon? true}) + :table (: (view arg {:one-line? true + :prefer-colon? true}) + ;; transform {:key key} to {: key} + :gsub ":([%w?_-]+) ([%w?]+)([ }])" + #(if (= $1 $2) + (.. ": " $2 $3))) _ (tostring arg))) -(λ fn-signature-format [name args] +(fn fn-signature-format [special name args] (let [args (case (type (?. args 1)) :table (icollect [_ v (ipairs args)] (render-arg v)) _ args)] (.. "(" - (tostring name) " " + (tostring (or name special)) " " (table.concat args " ") ")"))) (fn fn-format [special name args docstring] - (.. (code-block (.. "(" - (tostring special) - (if name (.. " " (tostring name)) "") - (.. " " - (: (view args - {:empty-as-sequence? true - :one-line? true - :prefer-colon? true}) - :gsub ":([%w?_-]+) ([%w?]+)([ }])" - #(if (= $1 $2) - (.. ": " $2 $3)))) - " ...)")) - (if docstring (.. "\n" docstring) ""))) + (.. (code-block (fn-signature-format special name args)) + (if docstring (.. "\n---\n" docstring) ""))) (fn metadata-format [{: binding : metadata}] "formats a special using its builtin metadata magic" @@ -49,7 +42,7 @@ user code. Fennel-ls doesn't support user-code formatting as of now." (= 0 (length metadata.fnl/arglist)) (.. "(" (tostring binding) ")") (.. "(" (tostring binding) " " (table.concat metadata.fnl/arglist " ") ")"))) - "\n" + "\n---\n" (or metadata.fnl/docstring ""))) (λ fn? [symbol] @@ -99,15 +92,15 @@ fntype is one of fn or λ or lambda" symbol can be an actual ast symbol or a binding object from a docset" (case (analyze-fn symbol.definition) - {:name ?name :arglist ?arglist :docstring ?docstring} - {:label (fn-signature-format ?name ?arglist) + {:fntype ?fntype :name ?name :arglist ?arglist :docstring ?docstring} + {:label (fn-signature-format ?fntype ?name ?arglist) :documentation ?docstring :parameters (if ?arglist (icollect [_ arg (ipairs ?arglist)] {:label (render-arg arg)}))} _ (case symbol {: binding :metadata {:fnl/arglist arglist :fnl/docstring docstring}} - {:label (fn-signature-format binding arglist) + {:label (fn-signature-format :fn binding arglist) :documentation docstring} _ {:label (.. "ERROR: don't know how to format " (tostring symbol)) :documentation (code-block diff --git a/src/fennel-ls/handlers.fnl b/src/fennel-ls/handlers.fnl index b8bc15f..b64513f 100644 --- a/src/fennel-ls/handlers.fnl +++ b/src/fennel-ls/handlers.fnl @@ -140,8 +140,9 @@ Every time the client sends a message, it gets handled by a function in the corr (λ requests.textDocument/signatureHelp [server _send {:textDocument {: uri} : position}] - (let [file (files.get-by-uri server uri)] - (case-try (analyzer.find-nearest-call server file position) + (let [file (files.get-by-uri server uri) + byte (utils.position->byte file.text position server.position-encoding)] + (case-try (analyzer.find-nearest-call server file byte) (symbol active-parameter) (analyzer.find-nearest-definition server file symbol) {:indeterminate nil &as result} diff --git a/test/hover.fnl b/test/hover.fnl index 48b84cb..69fdea8 100644 --- a/test/hover.fnl +++ b/test/hover.fnl @@ -33,13 +33,14 @@ nil) (fn test-builtins [] - (check "(d|o nil)" "```fnl\n(do ...)\n```\nEvaluate multiple forms; return last value.") - (check "(|doto nil (print))" "```fnl\n(doto val ...)\n```\nEvaluate val and splice it into the first argument of subsequent forms.") - (check "(le|t [x 10] 10)" "```fnl\n(let [name1 val1 ... nameN valN] ...)\n```\nIntroduces a new scope in which a given set of local bindings are used.") + (check "(d|o nil)" "```fnl\n(do ...)\n```\n---\nEvaluate multiple forms; return last value.") + (check "(|doto nil (print))" "```fnl\n(doto val ...)\n```\n---\nEvaluate val and splice it into the first argument of subsequent forms.") + (check "(le|t [x 10] 10)" "```fnl\n(let [name1 val1 ... nameN valN] ...)\n```\n---\nIntroduces a new scope in which a given set of local bindings are used.") nil) (fn test-globals [] (check "(pri|nt :hello :world)" "```fnl\n(print ...)\n``` +--- Receives any number of arguments and prints their values to `stdout`, converting each argument to a string @@ -51,6 +52,7 @@ for instance for debugging. For complete control over the output, use `string.format` and `io.write`.") (check "(local x print) (x| :hello :world)" "```fnl\n(print ...)\n``` +--- Receives any number of arguments and prints their values to `stdout`, converting each argument to a string @@ -64,6 +66,7 @@ use `string.format` and `io.write`.") (check "(xpca|ll io.open debug.traceback :filename.txt)" "```fnl (xpcall f msgh ?arg1 ...) ``` +--- This function is similar to `pcall`, except that it sets a new message handler `msgh`.") (check "(table.inser|t [] :message" #($:find "```fnl\n(table.insert list value)\n```" 1 true)) @@ -71,43 +74,43 @@ except that it sets a new message handler `msgh`.") (fn test-module [] (check "coroutine.yie|ld" - "```fnl\n(coroutine.yield ...)\n```\nSuspends the execution of the calling coroutine.\nAny arguments to `yield` are passed as extra results to `resume`.") + "```fnl\n(coroutine.yield ...)\n```\n---\nSuspends the execution of the calling coroutine.\nAny arguments to `yield` are passed as extra results to `resume`.") (check "string.cha|r" - "```fnl\n(string.char ...)\n```\nReceives zero or more integers.\nReturns a string with length equal to the number of arguments,\nin which each character has the internal numeric code equal\nto its corresponding argument.\n\nNumeric codes are not necessarily portable across platforms.") + "```fnl\n(string.char ...)\n```\n---\nReceives zero or more integers.\nReturns a string with length equal to the number of arguments,\nin which each character has the internal numeric code equal\nto its corresponding argument.\n\nNumeric codes are not necessarily portable across platforms.") (check "(local x :hello) x.cha|r" - "```fnl\n(string.char ...)\n```\nReceives zero or more integers.\nReturns a string with length equal to the number of arguments,\nin which each character has the internal numeric code equal\nto its corresponding argument.\n\nNumeric codes are not necessarily portable across platforms.")) + "```fnl\n(string.char ...)\n```\n---\nReceives zero or more integers.\nReturns a string with length equal to the number of arguments,\nin which each character has the internal numeric code equal\nto its corresponding argument.\n\nNumeric codes are not necessarily portable across platforms.")) (fn test-functions [] (check "(fn my-function| [arg1 arg2 arg3] (print arg1 arg2 arg3))" - "```fnl\n(fn my-function [arg1 arg2 arg3] ...)\n```") + "```fnl\n(my-function arg1 arg2 arg3)\n```") (check "(fn my-function| [arg1 arg2 arg3] \"this is a doc string\" (print arg1 arg2 arg3))" - "```fnl\n(fn my-function [arg1 arg2 arg3] ...)\n```\nthis is a doc string") + "```fnl\n(my-function arg1 arg2 arg3)\n```\n---\nthis is a doc string") (check "(fn my-function [arg1 arg2 arg3] \"this is a doc string\" (print arg1 arg2 arg3)) (|my-function)" - "```fnl\n(fn my-function [arg1 arg2 arg3] ...)\n```\nthis is a doc string") + "```fnl\n(my-function arg1 arg2 arg3)\n```\n---\nthis is a doc string") (check "(fn my-function [arg1 arg2 arg3] \"this is a doc string\" (print arg1 arg2 arg3)) (my-function)|" nil) (check "(fn foo| [x ...] \"not a docstring, this gets returned\")" - "```fnl\n(fn foo [x ...] ...)\n```") + "```fnl\n(foo x ...)\n```") (check "(λ foo| [x ...] \"not a docstring, this gets returned\")" - "```fnl\n(λ foo [x ...] ...)\n```") + "```fnl\n(foo x ...)\n```") (check "(λ foo| [{: start : end}] :body)" - "```fnl\n(λ foo [{: end : start}] ...)\n```") + "```fnl\n(foo {: end : start})\n```") (check "(λ foo| [{:list [a b c] :table {: d : e : f}}] :body)" - "```fnl\n(λ foo [{:list [a b c] :table {: d : e : f}}] ...)\n```") + "```fnl\n(foo {:list [a b c] :table {: d : e : f}})\n```") nil) (fn test-multisym [] @@ -150,7 +153,7 @@ except that it sets a new message handler `msgh`.") \"docstring!\" `(,a ,b ,c)) (fo|o print :hello :world)" - "```fnl\n(foo a b c)\n```\ndocstring!") + "```fnl\n(foo a b c)\n```\n---\ndocstring!") ; (check {:main.fnl "(import-macros cool :cool) ; (coo|l.=)" ; :cool.fnl ";; fennel-ls: macro-file