~xerool/fennel-ls

This thread contains a patchset. You're looking at the original emails, but you may wish to use the patch review UI. Review patch
3 3

[PATCH 1/2] Implement basic signatureHelp feature.

Details
Message ID
<20250321161408.24102-1-micampe@micampe.it>
Sender timestamp
1742577247
DKIM signature
pass
Download raw message
Patch: +181 -25
This implements the simple form of the signatureHelp feature, which only
displays the signature of the function being typed, without indication
of the active argument.

Active argument detection, while accounting for destructuring to support
each and for special forms turned out to be more involved than expected
and is left for a follow up patch.
---
 changelog.md                |  1 +
 src/fennel-ls/analyzer.fnl  | 21 +++++++---
 src/fennel-ls/compiler.fnl  | 19 +++++++++-
 src/fennel-ls/formatter.fnl | 41 +++++++++++++++++++-
 src/fennel-ls/handlers.fnl  | 17 ++++++++-
 src/fennel-ls/lint.fnl      | 16 +-------
 src/fennel-ls/message.fnl   |  6 +++
 test/signature-help.fnl     | 76 +++++++++++++++++++++++++++++++++++++
 test/utils/client.fnl       |  9 +++++
 9 files changed, 181 insertions(+), 25 deletions(-)
 create mode 100644 test/signature-help.fnl

diff --git a/changelog.md b/changelog.md
index 7dd55cd..b885f9b 100644
--- a/changelog.md
+++ b/changelog.md
@@ -4,6 +4,7 @@

### Features

* Signature help support.
* Provide human readable code actions titles.
* Add --help and --version command line flags.
* Support providing improved completion kinds to clients.
diff --git a/src/fennel-ls/analyzer.fnl b/src/fennel-ls/analyzer.fnl
index 0739e31..b048359 100644
--- a/src/fennel-ls/analyzer.fnl
+++ b/src/fennel-ls/analyzer.fnl
@@ -39,12 +39,11 @@ find the definition `10`, but if `opts.stop-early?` is set, it would find
"

(local {: sym? : list? : sequence? : varg?} (require :fennel))
(local utils (require :fennel-ls.utils))
(local {: special?} (require :fennel-ls.compiler))
(local {: get-ast-info &as utils} (require :fennel-ls.utils))
(local files (require :fennel-ls.files))
(local docs (require :fennel-ls.docs))

(local get-ast-info utils.get-ast-info)

(var search-multival nil) ;; all of the search functions are mutually recursive

(λ stack-add-keys! [stack ?keys]
@@ -222,9 +221,8 @@ find the definition `10`, but if `opts.stop-early?` is set, it would find
(λ _past? [?ast byte]
  ;; check if a byte is past an ast object
  (and (= (type ?ast) :table)
       (get-ast-info ?ast :bytestart)
       (< byte (get-ast-info ?ast :bytestart))
       false))
       (get-ast-info ?ast :byteend)
       (< (get-ast-info ?ast :byteend) byte)))

(λ contains? [?ast byte]
  ;; check if an ast contains a byte
@@ -276,12 +274,23 @@ 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 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)]
    (if (special? parent)
        (values parent -1)
        (values call -1))))

(λ find-nearest-definition [server file symbol ?byte]
  (if (. file.definitions symbol)
    (. file.definitions symbol)
    (search-main server file symbol {:stop-early? true} {:byte ?byte})))

{: find-symbol
 : find-nearest-call
 : find-nearest-definition
 : search-main
 : search-name-and-scope
diff --git a/src/fennel-ls/compiler.fnl b/src/fennel-ls/compiler.fnl
index f571564..1780a69 100644
--- a/src/fennel-ls/compiler.fnl
+++ b/src/fennel-ls/compiler.fnl
@@ -6,6 +6,7 @@ compiler's plugin hook callbacks. It stores lexical info about which
identifiers are declared / referenced in which places."

(local {: sym? : list? : sequence? : table? : sym : view &as fennel} (require :fennel))
(local {:scopes {:global {: specials}}} (require :fennel.compiler))
(local docs (require :fennel-ls.docs))
(local message (require :fennel-ls.message))
(local searcher (require :fennel-ls.searcher))
@@ -13,6 +14,20 @@ identifiers are declared / referenced in which places."

(local nil* (sym :nil))

(fn special? [item]
  (and (sym? item)
       (. specials (tostring item))
       item))

(local ops {"+" 1 "-" 1 "*" 1 "/" 1 "//" 1 "%" 1 "^" 1 ">" 1 "<" 1 ">=" 1
            "<=" 1 "=" 1 "not=" 1 ".." 1 "." 1 "and" 1 "or" 1 "band" 1
            "bor" 1 "bxor" 1 "bnot" 1 "lshift" 1 "rshift" 1})

(fn op? [item]
  (and (sym? item)
       (. ops (tostring item))
       item))

(fn scope? [candidate]
  ;; just checking a couple of the fields
  (and
@@ -426,4 +441,6 @@ identifiers are declared / referenced in which places."
      (set file.macro-refs macro-refs)
      (set file.macro-calls macro-calls))))

{: compile}
{: special?
 : op?
 : compile}
diff --git a/src/fennel-ls/formatter.fnl b/src/fennel-ls/formatter.fnl
index 864ef55..0eb2b96 100644
--- a/src/fennel-ls/formatter.fnl
+++ b/src/fennel-ls/formatter.fnl
@@ -9,6 +9,22 @@ user code. Fennel-ls doesn't support user-code formatting as of now."
(λ code-block [str]
  (.. "```fnl\n" str "\n```"))

(λ render-arg [arg]
  (case (type arg)
    :table (view arg {:one-line? true
                      :prefer-colon? true})
    _ (tostring arg)))

(λ fn-signature-format [name args]
  (let [args (case (type (?. args 1))
               :table (icollect [_ v (ipairs args)]
                        (render-arg v))
               _ args)]
    (.. "("
        (tostring name) " "
        (table.concat args " ")
        ")")))

(fn fn-format [special name args docstring]
  (.. (code-block (.. "("
                      (tostring special)
@@ -78,12 +94,32 @@ fntype is one of fn or λ or lambda"
      (= (type arglist) :table))
    {: fntype : arglist}))

(λ signature-help-format [symbol]
  "Return a signatureHelp lsp object

  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)
     :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)
         :documentation docstring}
        _ {:label (.. "ERROR: don't know how to format " (tostring symbol))
           :documentation (code-block
                            (view symbol {:depth 3}))})))

(λ hover-format [result]
  "Format code that will appear when the user hovers over a symbol"
  {:kind "markdown"
   :value
   (case (analyze-fn result.definition)
     {:fntype ?fntype :name ?name :arglist ?arglist :docstring ?docstring} (fn-format ?fntype ?name ?arglist ?docstring)
     {:fntype ?fntype :name ?name :arglist ?arglist :docstring ?docstring}
     (fn-format ?fntype ?name ?arglist ?docstring)
     _ (if (-?>> result.keys length (< 0))
         (code-block
           (.. "ERROR, I don't know how to show this "
@@ -112,5 +148,6 @@ fntype is one of fn or λ or lambda"
         :kind (. kinds (?. result :metadata :fls/itemKind))})
    (tset :documentation (hover-format result))))

{: hover-format
{: signature-help-format
 : hover-format
 : completion-item-format}
diff --git a/src/fennel-ls/handlers.fnl b/src/fennel-ls/handlers.fnl
index 3ea6d10..b8bc15f 100644
--- a/src/fennel-ls/handlers.fnl
+++ b/src/fennel-ls/handlers.fnl
@@ -40,7 +40,9 @@ Every time the client sends a message, it gets handled by a function in the corr
                              :triggerCharacters ["(" "[" "{" "." ":" "\""]
                              :completionItem {:labelDetailsSupport false}}
         :hoverProvider {:workDoneProgress false}
         ;; :signatureHelpProvider nil
         :signatureHelpProvider {:workDoneProgress false
                                 :triggerCharacters [" "]
                                 :retriggerCharacters [" "]}
         ;; :declarationProvider nil
         :definitionProvider {:workDoneProgress false}
         ;; :typeDefinitionProvider nil
@@ -135,6 +137,19 @@ Every time the client sends a message, it gets handled by a function in the corr
        result)
      (catch _ nil))))

(λ 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)
      (symbol active-parameter)
      (analyzer.find-nearest-definition server file symbol)
      {:indeterminate nil &as result}
      (message.symbol->signature-help server file symbol
                                      (formatter.signature-help-format result)
                                      active-parameter)
      (catch _ nil))))

(λ requests.textDocument/hover [server _send {: position :textDocument {: uri}}]
  (let [file (files.get-by-uri server uri)
        byte (utils.position->byte file.text position server.position-encoding)]
diff --git a/src/fennel-ls/lint.fnl b/src/fennel-ls/lint.fnl
index e29e8f0..168d202 100644
--- a/src/fennel-ls/lint.fnl
+++ b/src/fennel-ls/lint.fnl
@@ -4,7 +4,7 @@ the `file.diagnostics` field, filling it with diagnostics."

(local {: sym? : list? : table? : view
        : sym : list &as fennel} (require :fennel))
(local {:scopes {:global {: specials}}} (require :fennel.compiler))
(local {: special? : op?} (require :fennel-ls.compiler))
(local analyzer (require :fennel-ls.analyzer))
(local message (require :fennel-ls.message))
(local utils (require :fennel-ls.utils))
@@ -16,20 +16,6 @@ the `file.diagnostics` field, filling it with diagnostics."
(fn diagnostic [self quickfix]
  (setmetatable {: self : quickfix} diagnostic-mt))

(local ops {"+" 1 "-" 1 "*" 1 "/" 1 "//" 1 "%" 1 "^" 1 ">" 1 "<" 1 ">=" 1
            "<=" 1 "=" 1 "not=" 1 ".." 1 "." 1 "and" 1 "or" 1 "band" 1
            "bor" 1 "bxor" 1 "bnot" 1 "lshift" 1 "rshift" 1})

(fn special? [item]
  (and (sym? item)
       (. specials (tostring item))
       item))

(fn op? [item]
  (and (sym? item)
       (. ops (tostring item))
       item))

(fn could-be-rewritten-as-sym? [str]
  (and (= :string (type str)) (not (str:find "^%d"))
       (not (str:find "[^!$%*+/0-9<=>?A-Z\\^_a-z|\128-\255-]"))))
diff --git a/src/fennel-ls/message.fnl b/src/fennel-ls/message.fnl
index 9b3205a..a142a6c 100644
--- a/src/fennel-ls/message.fnl
+++ b/src/fennel-ls/message.fnl
@@ -91,6 +91,11 @@ LSP json objects."
     :kind ?kind
     :edit {:changes {uri (diagnostic.quickfix)}}}))

(λ symbol->signature-help [_server _file _call signature active-parameter]
  {:signatures [signature]
   :activeSignature 0
   :activeParameter active-parameter})

(λ multisym->range [server file ast n]
  (let [spl (utils.multi-sym-split ast)
        n (if (< n 0) (+ n 1 (length spl)) n)]
@@ -129,6 +134,7 @@ LSP json objects."
 : create-error
 : ast->range
 : diagnostic->code-action
 : symbol->signature-help
 : multisym->range
 : range-and-uri
 : diagnostics
diff --git a/test/signature-help.fnl b/test/signature-help.fnl
new file mode 100644
index 0000000..f3e459d
--- /dev/null
+++ b/test/signature-help.fnl
@@ -0,0 +1,76 @@
(local faith (require :faith))
(local {: view} (require :fennel))
(local {: create-client} (require :test.utils))

(λ check-signature [expected response]
  (case response
    {:signatures [{:label signature}]}
    (faith.= expected.signature signature)
    ;; fail
    _ (faith.is nil (.. "Invalid response: " (view response)))))

(fn check [file-contents expected]
  (let [{: client : uri : cursor} (create-client file-contents)
        [{: result}] (client:signature-help uri cursor)]
    (check-signature expected result)))

(fn test-fn-definition []
  (check "(fn func [arg1 arg2] (print :hello))
          (func|)"
         {:signature "(func arg1 arg2)"})

  (check "(fn func [arg1 arg2] (print :hello))
          (func |)"
         {:signature "(func arg1 arg2)"})

  (check "(fn func [arg1 arg2] (print :hello))
          (func a1|)"
         {:signature "(func arg1 arg2)"})

  (check "(fn func [arg1 arg2] (print :hello))
          (func a|1 a2)"
         {:signature "(func arg1 arg2)"})

  (check "(fn func [arg1 arg2] (print :hello))
          (func a1 a2|)"
         {:signature "(func arg1 arg2)"})

  (check "(fn func [arg1 arg2] (print :hello))
          (func a1 a|2)"
         {:signature "(func arg1 arg2)"}))

(fn test-lua-builtin []
  (check "(error msg lvl|)"
         {:signature "(error message ?level)"}))

(fn test-multisym []
  (check "(table.concat tbl s|)"
         {:signature "(table.concat list ?sep ?i ?j)"}))

(fn test-destructuring-arg []
  (check "(fn dstr [{:field name} arg2] {})
          (dstr |)"
         {:signature "(dstr {:field name} arg2)"})

  (check "(fn dstr [{:field name} arg2] {})
          (dstr arg1 ar|)"
         {:signature "(dstr {:field name} arg2)"}))

(fn test-special []
  (check "(each |)"
         {:signature "(each [key value (iterator)] ...)"})

  (check "(each [|])"
         {:signature "(each [key value (iterator)] ...)"})

  (check "(each [k val|])"
         {:signature "(each [key value (iterator)] ...)"})

  (check "(each [33|])"
         {:signature "(each [key value (iterator)] ...)"}))

{: test-fn-definition
 : test-lua-builtin
 : test-multisym
 : test-destructuring-arg
 : test-special}
diff --git a/test/utils/client.fnl b/test/utils/client.fnl
index 37a07d2..d1dc210 100644
--- a/test/utils/client.fnl
+++ b/test/utils/client.fnl
@@ -55,6 +55,14 @@
     {: position
      :textDocument {:uri file}})))

(fn signature-help [self file position]
  (dispatch.handle*
    self.server
    (message.create-request (next-id! self)
                            :textDocument/signatureHelp
                            {: position
                             :textDocument {:uri file}})))

(fn rename [self file position newName]
  (dispatch.handle* self.server
    (message.create-request (next-id! self) :textDocument/rename
@@ -83,6 +91,7 @@
             : hover
             : references
             : document-highlight
             : signature-help
             : rename
             : code-action
             : did-save}})
-- 
2.39.5 (Apple Git-154)

[PATCH 2/2] Format function signatures from ast and metadata the same.

Details
Message ID
<20250321161408.24102-2-micampe@micampe.it>
In-Reply-To
<20250321161408.24102-1-micampe@micampe.it> (view parent)
Sender timestamp
1742577248
DKIM signature
pass
Download raw message
Patch: +36 -40
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
-- 
2.39.5 (Apple Git-154)
Details
Message ID
<87y0wys1ll.fsf@asthra>
In-Reply-To
<20250321161408.24102-1-micampe@micampe.it> (view parent)
Sender timestamp
1742554236
DKIM signature
pass
Download raw message
Michele Campeotto <micampe@micampe.it> writes:


> @@ -222,9 +221,8 @@ find the definition `10`, but if `opts.stop-early?` is set, it would find
>  (λ _past? [?ast byte]
>    ;; check if a byte is past an ast object
>    (and (= (type ?ast) :table)
> -       (get-ast-info ?ast :bytestart)
> -       (< byte (get-ast-info ?ast :bytestart))
> -       false))
> +       (get-ast-info ?ast :byteend)
> +       (< (get-ast-info ?ast :byteend) byte)))

This is not a problem, but I'm curious why you edited this function that
never actually gets called. I'm assuming it's useful for debugging or
something, but it should probably be documented why we haven't deleted
it if it's actually useful but not used.

Overall it looks good. I have one question; this might just be a weird
client thing, but when I put the point over a top-level fn, for example,
it shows the help twice in the bottom of the screen:

    (fn name? args docstring? ...)
    (fn name? args docstring? ...)

But when I put it on a call inside a function like check for example, I
see this:

    (fn name? args docstring? ...)
    (check file-contents expected)

So first of all, is it intentional to show multiple lines like this? The
Clojure language server is the only other one I've used, and it shows
the signature on the first line and then the location of the function on
the second line; it never repeats a line nor shows info about different
levels of nesting. Do other clients do something similar here or is it
an Emacs thing?

Well anyway, none of that is a problem, so I went ahead and applied the
patch. Thanks again!

-Phil
Details
Message ID
<D8MASKNC8P60.2UTT4DMHEBMSV@micampe.it>
In-Reply-To
<87y0wys1ll.fsf@asthra> (view parent)
Sender timestamp
1742599786
DKIM signature
pass
Download raw message
On Fri Mar 21, 2025 at 6:50 PM CET, Phil Hagelberg wrote:
> This is not a problem, but I'm curious why you edited this function that
> never actually gets called. I'm assuming it's useful for debugging or
> something, but it should probably be documented why we haven't deleted
> it if it's actually useful but not used.

I am using that function in the full implementation to detect the active
argument given the cursor position. I left the change in when I
extracted this simplified version because I wasted some time before
noticing there was a 'false' in that (and)...

> So first of all, is it intentional to show multiple lines like this? The
> Clojure language server is the only other one I've used, and it shows
> the signature on the first line and then the location of the function on
> the second line; it never repeats a line nor shows info about different
> levels of nesting. Do other clients do something similar here or is it
> an Emacs thing?

It's not intentional, this feature only sends a single signature and
it's just a string, no context, I think the location part in Clojure is
a client thing, it's not part of the signatureHelp response, so I'm not
sure why you get the two lines. Maybe I'll try to get emacs or helix set
up to see how it behaves in other clients.

>     (fn name? args docstring? ...)

This is returned when you are inside a function definition.

>     (check file-contents expected)

This is returned when you inside the call to the check function.

> Well anyway, none of that is a problem, so I went ahead and applied the
> patch. Thanks again!

Thanks you!


michele
Reply to thread Export thread (mbox)