diff --git a/src/fennel-ls/compiler.fnl b/src/fennel-ls/compiler.fnl index b545c25..5512665 100644 --- a/src/fennel-ls/compiler.fnl +++ b/src/fennel-ls/compiler.fnl @@ -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 diff --git a/src/fennel-ls/completion.fnl b/src/fennel-ls/completion.fnl index 539d468..fb12a65 100644 --- a/src/fennel-ls/completion.fnl +++ b/src/fennel-ls/completion.fnl @@ -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} diff --git a/src/fennel-ls/formatter.fnl b/src/fennel-ls/formatter.fnl index 8c4e4f3..917a859 100644 --- a/src/fennel-ls/formatter.fnl +++ b/src/fennel-ls/formatter.fnl @@ -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 diff --git a/src/fennel-ls/handlers.fnl b/src/fennel-ls/handlers.fnl index 342b89f..13cab31 100644 --- a/src/fennel-ls/handlers.fnl +++ b/src/fennel-ls/handlers.fnl @@ -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)))) diff --git a/src/fennel-ls/navigate.fnl b/src/fennel-ls/navigate.fnl new file mode 100644 index 0000000..c026776 --- /dev/null +++ b/src/fennel-ls/navigate.fnl @@ -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} diff --git a/src/fennel-ls/utils.fnl b/src/fennel-ls/utils.fnl index 663a8f1..104ea26 100644 --- a/src/fennel-ls/utils.fnl +++ b/src/fennel-ls/utils.fnl @@ -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 diff --git a/test/hover.fnl b/test/hover.fnl index 69fdea8..45499da 100644 --- a/test/hover.fnl +++ b/test/hover.fnl @@ -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!\"