rework hovering
This commit is contained in:
parent
0641b1b098
commit
1e8784a55a
@ -5,7 +5,7 @@ fennel compiler, and then tries to store into it gets from the fennel
|
||||
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 {: sym? : list? : sequence? : table? : varg? : 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))
|
||||
@ -396,7 +396,7 @@ identifiers are declared / referenced in which places."
|
||||
(table.insert defer #(set ast.byteend ast.bytestart))
|
||||
(where (:unquote ","))
|
||||
(table.insert defer #(set ast.byteend ast.bytestart))))
|
||||
(when (or (table? ast) (list? ast) (sym? ast))
|
||||
(when (or (table? ast) (list? ast) (sym? ast) (varg? ast))
|
||||
(tset lexical ast true))
|
||||
;; recursive call
|
||||
(when (or (table? ast) (list? ast))
|
||||
@ -410,7 +410,7 @@ identifiers are declared / referenced in which places."
|
||||
(tset ast 1 (sym :fn))
|
||||
(table.insert defer #(tset ast 1 old-sym)))))
|
||||
|
||||
(parsed ast lexical)
|
||||
(parsed ast)
|
||||
|
||||
;; This is bad; we have to mutate fennel.macro-path to use fennel's native macro loader
|
||||
(let [old-macro-path fennel.macro-path
|
||||
|
||||
@ -99,7 +99,7 @@
|
||||
(. 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 result))))))
|
||||
result (doto completion-item (tset :documentation (format.hover-format server completion-item.label result))))))
|
||||
|
||||
{: textDocument/completion
|
||||
: completionItem/resolve}
|
||||
|
||||
@ -5,7 +5,12 @@ user code. Fennel-ls doesn't support user-code formatting as of now."
|
||||
|
||||
(local {: sym?
|
||||
: view
|
||||
: table?} (require :fennel))
|
||||
: table?
|
||||
: varg?
|
||||
: list? &as fennel} (require :fennel))
|
||||
|
||||
(local navigate (require :fennel-ls.navigate))
|
||||
(local utils (require :fennel-ls.utils))
|
||||
|
||||
(λ code-block [str]
|
||||
(.. "```fnl\n" str "\n```"))
|
||||
@ -26,15 +31,16 @@ user code. Fennel-ls doesn't support user-code formatting as of now."
|
||||
(render-arg [:foo]) -> \"[foo]\"
|
||||
(render-arg [(sym :foo)]) -> \"[foo]\""
|
||||
(if (table? arg)
|
||||
(: (view (collect [k v (pairs arg)]
|
||||
;; we don't want to view `v` because it's already been rendered
|
||||
k (unview (render-arg v)))
|
||||
{:one-line? true
|
||||
:prefer-colon? true})
|
||||
;; transform {:key key} to {: key}
|
||||
:gsub ":([%w?_-]+) ([%w?_-]+)([ }])"
|
||||
#(if (= $1 $2)
|
||||
(.. ": " $2 $3)))
|
||||
(pick-values 1
|
||||
(: (view (collect [k v (pairs arg)]
|
||||
;; we don't want to view `v` because it's already been rendered
|
||||
k (unview (render-arg v)))
|
||||
{:one-line? true
|
||||
:prefer-colon? true})
|
||||
;; transform {:key key} to {: key}
|
||||
:gsub ":([%w?_-]+) ([%w?_-]+)([ }])"
|
||||
#(if (= $1 $2)
|
||||
(.. ": " $2 $3))))
|
||||
(tostring arg)))
|
||||
|
||||
(fn fn-signature-format [special ?name args]
|
||||
@ -147,24 +153,49 @@ fntype is one of fn or λ or lambda"
|
||||
(view doc-or-definition {:one-line? true :depth 3}))
|
||||
:documentation (code-block
|
||||
(view doc-or-definition {:depth 3}))})))))
|
||||
(λ get-stub [server name definition ?short]
|
||||
"gets a string representation of this object"
|
||||
(or (case definition.definition
|
||||
ast (case (type ast)
|
||||
(where _ (varg? ast)) (view ast)
|
||||
(where _ (sym? ast :nil)) "nil"
|
||||
(where (or :string :number :boolean)) (view ast {:prefer-colon? true})
|
||||
(where _ (table? ast))
|
||||
(if ?short
|
||||
"{...}"
|
||||
(let [t (collect [k v (navigate.iter-fields server definition)]
|
||||
k (unview (get-stub server (.. name "." k) v true)))]
|
||||
(view t)))))
|
||||
(case definition.definition
|
||||
(where [hfn body]
|
||||
(list? definition.definition)
|
||||
(sym? hfn :hashfn))
|
||||
(.. "#" (view body {:one-line? true})))
|
||||
(case (navigate.getmetadata server definition)
|
||||
metadata
|
||||
(if metadata.fnl/arglist
|
||||
(.. "("
|
||||
(table.concat (icollect [_ arg (ipairs metadata.fnl/arglist) &into [name]]
|
||||
(render-arg arg))
|
||||
" ")
|
||||
")")
|
||||
?short
|
||||
"{...}"
|
||||
(let [t (collect [k v (navigate.iter-fields server definition)]
|
||||
k (unview (get-stub server (.. name "." k) v true)))]
|
||||
(view t {:prefer-colon true}))))
|
||||
(when definition.indeterminate
|
||||
"?")
|
||||
(view (or definition.binding definition.definition))))
|
||||
|
||||
(λ hover-format [result]
|
||||
(λ hover-format [server name definition]
|
||||
"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)
|
||||
_ (if (-?>> result.keys length (not= 0))
|
||||
(code-block
|
||||
(.. "ERROR, I don't know how to show this "
|
||||
"(. "
|
||||
(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}))))})
|
||||
:value (.. (code-block (get-stub server name definition))
|
||||
(or (-?> (navigate.getmetadata server definition)
|
||||
(. :fnl/docstring)
|
||||
(->> (.. "\n---\n")))
|
||||
""))})
|
||||
|
||||
;; CompletionItemKind
|
||||
(local kinds
|
||||
@ -176,13 +207,15 @@ fntype is one of fn or λ or lambda"
|
||||
(λ completion-item-format [server name definition range ?kind]
|
||||
"Makes a completion item"
|
||||
{:label name
|
||||
:documentation (when (not server.can-do-good-completions?) (hover-format definition))
|
||||
: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 (when ?kind (. kinds ?kind))
|
||||
(. kinds (?. definition :metadata :fls/itemKind))
|
||||
(when (or (?. definition :metadata :fnl/arglist)
|
||||
(?. (analyze-fn definition.definition)) :fntype)
|
||||
(if (name:find ":") kinds.Method kinds.Function)))})
|
||||
:kind (or (?. 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.Text)})
|
||||
|
||||
{: signature-help-format
|
||||
: hover-format
|
||||
|
||||
@ -157,7 +157,7 @@ Every time the client sends a message, it gets handled by a function in the corr
|
||||
byte (utils.position->byte file.text position server.position-encoding)]
|
||||
(case-try (analyzer.find-symbol file.ast byte)
|
||||
symbol (analyzer.search server file symbol {} {: byte})
|
||||
{:indeterminate nil &as result} {:contents (formatter.hover-format result)
|
||||
{:indeterminate nil &as result} {:contents (formatter.hover-format server (tostring symbol) result)
|
||||
:range (message.ast->range server file
|
||||
symbol)}
|
||||
(catch _ nil))))
|
||||
|
||||
56
src/fennel-ls/navigate.fnl
Normal file
56
src/fennel-ls/navigate.fnl
Normal file
@ -0,0 +1,56 @@
|
||||
(local docs (require :fennel-ls.docs))
|
||||
(local analyzer (require :fennel-ls.analyzer))
|
||||
(local fennel (require :fennel))
|
||||
|
||||
(λ iter-fields [server definition]
|
||||
"TODO name this thing"
|
||||
(coroutine.wrap
|
||||
#(if (= (type definition.definition) :string)
|
||||
(each [key value (pairs (-> (docs.get-global server :string) (. :fields)))]
|
||||
(when (or (= (type key) :string) (= (type key) :number))
|
||||
(coroutine.yield key value true)))
|
||||
(do
|
||||
(when (fennel.table? definition.definition)
|
||||
(each [key value (pairs definition.definition)]
|
||||
(when (or (= (type key) :string) (= (type key) :number))
|
||||
(let [value (analyzer.search server definition.file value [] {})]
|
||||
(if value
|
||||
(coroutine.yield key value)
|
||||
definition.fields)))))
|
||||
(when definition.fields
|
||||
(each [key value (pairs definition.fields)]
|
||||
(when (or (= (type key) :string) (= (type key) :number))
|
||||
(coroutine.yield key value))))))))
|
||||
|
||||
(λ get-field [server definition key]
|
||||
(let [fields (or definition.fields
|
||||
(when (type definition.definition) :string
|
||||
(. docs.get-global server :string)))]
|
||||
(or (?. fields key)
|
||||
(when (fennel.table? definition.definition)
|
||||
(analyzer.search server definition.file definition.definition {} {:stack [key]})))))
|
||||
|
||||
(λ getmetadata [server_ definition]
|
||||
"gets or generates a metadata table"
|
||||
(or definition.metadata
|
||||
(let [metadata (if (fennel.list? definition.definition)
|
||||
(case definition.definition
|
||||
;; we can only extract metadata for functions
|
||||
(where (or [fn* ?name arglist ?docstring body_]
|
||||
([fn* ?name arglist] ?docstring)
|
||||
([fn* arglist ?docstring body_] ?name)
|
||||
([fn* arglist] ?name ?docstring))
|
||||
(or (fennel.sym? fn* :fn) (fennel.sym? fn* :λ) (fennel.sym? fn* :lambda))
|
||||
(or (= ?name nil) (fennel.sym? ?name))
|
||||
(fennel.sequence? arglist)
|
||||
(or (= ?docstring nil) (= :string (type ?docstring))))
|
||||
{:fls/itemKind "Function"
|
||||
:fls/fntype (tostring fn*)
|
||||
:fnl/arglist arglist
|
||||
:fnl/docstring ?docstring}))]
|
||||
(set definition.metadata metadata)
|
||||
metadata)))
|
||||
|
||||
{: get-field
|
||||
: iter-fields
|
||||
: getmetadata}
|
||||
@ -33,10 +33,9 @@ These functions are all pure functions, which makes me happy."
|
||||
(values 4 2)
|
||||
(error :utf8-error)))
|
||||
|
||||
(fn byte->unit16 [str ?byte]
|
||||
(fn byte->unit16 [str byte]
|
||||
"convert from normal units to utf16 garbage"
|
||||
;; TODO reconsider this when upstream #180 is fixed
|
||||
(let [unit8 (math.min (length str) ?byte)]
|
||||
(let [unit8 byte]
|
||||
(var o8 0)
|
||||
(var o16 0)
|
||||
(while (< o8 unit8)
|
||||
@ -82,6 +81,12 @@ These functions are all pure functions, which makes me happy."
|
||||
:utf-16 {: line :character (byte->unit16 (str:sub pos) (- byte pos))}
|
||||
_ (error (.. "unknown encoding: " encoding))))
|
||||
|
||||
(λ byte->character [str byte encoding]
|
||||
(case encoding
|
||||
:utf-8 byte
|
||||
:utf-16 (byte->unit16 str byte)
|
||||
_ (error (.. "unknown encoding: " encoding))))
|
||||
|
||||
(λ position->byte [str {: line : character} encoding]
|
||||
"take an LSP position and convert it to a 1-indexed byte based on the given encoding"
|
||||
(let [pos (next-lines str line)]
|
||||
@ -218,6 +223,7 @@ WARNING: this is only used in the test code, not in the real language server"
|
||||
: path->uri
|
||||
: pos->position
|
||||
: byte->position
|
||||
: byte->character
|
||||
: position->byte
|
||||
: apply-changes
|
||||
: apply-edits
|
||||
|
||||
@ -51,7 +51,7 @@ but only as a quick way to show a value,
|
||||
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```
|
||||
(check "(local x print) (x| :hello :world)" "```fnl\n(x ...)\n```
|
||||
---
|
||||
Receives any number of arguments
|
||||
and prints their values to `stdout`,
|
||||
@ -79,7 +79,7 @@ except that it sets a new message handler `msgh`.")
|
||||
"```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```\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(x.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 []
|
||||
@ -147,7 +147,7 @@ except that it sets a new message handler `msgh`.")
|
||||
\"docstring!\"
|
||||
`(let [,a ,b] ,c))
|
||||
(bind x print |x)"
|
||||
#($:find "```fnl\n(print ...)\n```" 1 true))
|
||||
#($:find "```fnl\n(x ...)\n```" 1 true))
|
||||
|
||||
(check "(macro foo [a b c]
|
||||
\"docstring!\"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user