diff --git a/src/fennel-ls/compiler.fnl b/src/fennel-ls/compiler.fnl index fbe5dd1..5d0af63 100644 --- a/src/fennel-ls/compiler.fnl +++ b/src/fennel-ls/compiler.fnl @@ -67,10 +67,10 @@ later by fennel-ls.language to answer requests from the client." (if (sym? binding) (let [definition {: binding - : ?definition - :?keys (if (not= 0 (length keys)) - (fcollect [i 1 (length keys)] - (. keys i)))}] + :definition ?definition + :keys (if (< 0 (length keys)) + (fcollect [i 1 (length keys)] + (. keys i)))}] (tset (. definitions-by-scope scope) (tostring binding) definition) (tset definitions binding definition)) (= :table (type binding)) @@ -90,7 +90,7 @@ later by fennel-ls.language to answer requests from the client." (tset (. definitions-by-scope scope) ;; !!! TODO somehow insert into child scope (tostring name) {:binding name - :?definition ast}))) + :definition ast}))) (λ define-function-args [ast scope] ;; add the definitions of function arguments to the definitions @@ -99,18 +99,18 @@ later by fennel-ls.language to answer requests from the client." (where [_fn args] (fennel.sequence? args)) args (where [_fn _name args] (fennel.sequence? args)) args)) (each [_ argument (ipairs args)] - (define nil argument scope))) ;; we say function arguments are set to nil ;; !!! parent or child? + (define nil argument scope))) ;; we say function arguments are set to nil (λ define-function [ast scope] ;; handle the definitions of a function (define-function-name ast scope)) (λ compile-fn [ast scope] - (tset scopes ast scope) ;; update scope + (tset scopes ast scope) (define-function-args ast scope)) (λ compile-do [ast scope] - (tset scopes ast scope)) ;; update scope + (tset scopes ast scope)) (λ call [ast scope] (tset scopes ast scope) @@ -129,7 +129,8 @@ later by fennel-ls.language to answer requests from the client." (msg:find "expected closing delimiter") (msg:find "expected body expression") (msg:find "expected whitespace before opening delimiter") - (msg:find "malformed multisym"))) + (msg:find "malformed multisym") + (msg:find "expected at least one pattern/body pair"))) (λ on-compile-error [_ msg ast call-me-to-reset-the-compiler] (let [range (or (message.ast->range ast file) @@ -168,7 +169,7 @@ later by fennel-ls.language to answer requests from the client." {:name "fennel-ls" :versions ["1.3.0"] :symbol-to-expression reference - :call call + : call :destructure define :assert-compile on-compile-error :parse-error on-parse-error diff --git a/src/fennel-ls/formatter.fnl b/src/fennel-ls/formatter.fnl index c4d1ee4..64b53e7 100644 --- a/src/fennel-ls/formatter.fnl +++ b/src/fennel-ls/formatter.fnl @@ -17,40 +17,57 @@ user code." (.. "```fnl\n" str "\n```")) (local width 80) -(fn fn-format [name args docstring] - (.. "(fn" - (if name (.. " " (tostring name)) "") - (.. " " (view args {:one-line? true :prefer-colon? true})) - " ...)" +(fn fn-format [special name args docstring] + (.. (code-block (.. "(fn" + (if name (.. " " (tostring name)) "") + (.. " " (view args + {:one-line? true + :prefer-colon? true})) + " ...)")) (if docstring (.. "\n" docstring) ""))) + +(λ fn? [sym] + (if (sym? sym) + (let [sym (tostring sym)] + (or (= sym "fn") + (= sym "λ") + (= sym "lambda"))))) + (λ hover-format [result] "Format code that will appear when the user hovers over a symbol" - (code-block - (match result.?definition - ;; name + docstring - (where [-fn- name args docstring body] - (and (sym? name) - (type= args :table) - (type= docstring :string))) - (fn-format name args docstring) - ;; docstring - (where [-fn- args docstring body] - (and (type= args :table) - (type= docstring :string))) - (fn-format nil args docstring) - ;; name - (where [-fn- name args] - (and (sym? name) - (type= args :table))) - (fn-format name args nil) - ;; none - (where [-fn- args] - (and (type= args :table))) - (fn-format nil args nil) - ?anything-else - (if result.?keys - (view result.?keys) - (view ?anything-else {:prefer-colon? true}))))) + (match result.definition + ;; name + docstring + (where [special name args docstring body] + (fn? special) + (sym? name) + (type= args :table) + (type= docstring :string)) + (fn-format special name args docstring) + ;; docstring + (where [special args docstring body] + (fn? special) + (type= args :table) + (type= docstring :string)) + (fn-format special nil args docstring) + ;; name + (where [special name args] + (fn? special) + (sym? name) + (type= args :table)) + (fn-format special name args nil) + ;; none + (where [special args] + (fn? special) + (type= args :table)) + (fn-format special nil args nil) + ?anything-else + (code-block + (if (-?>> result.keys length (< 0)) + (.. "ERROR, I don't know how to show this " + "(. " + (view ?anything-else {:prefer-colon? true}) " " + (view result.keys {:prefer-colon? true}) ")") + (view ?anything-else {:prefer-colon? true}))))) {: hover-format} diff --git a/src/fennel-ls/handlers.fnl b/src/fennel-ls/handlers.fnl index 2ca0636..37105db 100644 --- a/src/fennel-ls/handlers.fnl +++ b/src/fennel-ls/handlers.fnl @@ -79,7 +79,7 @@ Every time the client sends a message, it gets handled by a function in the corr (language.search-main self file symbol)) (result result-file) (message.range-and-uri - (or result.binding result.?definition) + (or result.binding result.definition) result-file) (catch _ nil)))) @@ -108,15 +108,36 @@ Every time the client sends a message, it gets handled by a function in the corr file.scope)] (collect-scope scope typ callback ?target))) -(λ requests.textDocument/completion [self send {: position :textDocument {: uri}}] - (let [file (state.get-by-uri self uri) - byte (pos->byte file.text position.line position.character) - (?symbol parents) (language.find-symbol file.ast byte)] +(λ scope-completion [file byte ?symbol parents] (let [result []] (find-things-in-scope file parents :manglings #{:label $} result) (find-things-in-scope file parents :macros #{:label $} result) (find-things-in-scope file parents :specials #{:label $} result) - (icollect [_ k (ipairs file.allowed-globals) &into result] {:label k})))) + (icollect [_ k (ipairs file.allowed-globals) &into result] + {:label k}))) + +(λ field-completion [self file symbol split] + (match (. file.references symbol) + ref + (let [stack (fcollect [i (- (length split) 1) 2 -1] + (. split i))] + (match-try (language.search-assignment self file ref stack) + {: definition} + (match (values definition (type definition)) + (str :string) (icollect [k v (pairs string)] + {:label k}) + (tbl :table) (icollect [k v (pairs tbl)] + (if (= (type k) :string) + {:label k}))) + (catch _ nil))))) + +(λ requests.textDocument/completion [self send {: position :textDocument {: uri}}] + (let [file (state.get-by-uri self uri) + byte (pos->byte file.text position.line position.character) + (?symbol parents) (language.find-symbol file.ast byte)] + (match (-?> ?symbol utils.multi-sym-split) + (where (or nil [_ nil])) (scope-completion file byte ?symbol parents) + [a b &as split] (field-completion self file ?symbol split)))) (λ notifications.textDocument/didChange [self send {: contentChanges :textDocument {: uri}}] (local file (state.get-by-uri self uri)) diff --git a/src/fennel-ls/language.fnl b/src/fennel-ls/language.fnl index 2784f80..1c4f817 100644 --- a/src/fennel-ls/language.fnl +++ b/src/fennel-ls/language.fnl @@ -16,7 +16,8 @@ the data provided by compiler.fnl." (var search nil) ;; all of the search functions are mutually recursive -(λ search-assignment [self file {: binding : ?definition : ?keys &as assignment} stack] +(λ search-assignment [self file {: binding :definition ?definition :keys ?keys &as assignment} + stack] (if (= 0 (length stack)) (values assignment file) ;; BASE CASE!! (do @@ -36,7 +37,7 @@ the data provided by compiler.fnl." (if (. tbl (. stack (length stack))) (search self file (. tbl (table.remove stack)) stack) (= 0 (length stack)) - (values {:?definition tbl} file) ;; BASE CASE !! + (values {:definition tbl} file) ;; BASE CASE !! nil)) ;; BASE CASE Give up (λ search-list [self file call stack] @@ -64,7 +65,7 @@ the data provided by compiler.fnl." (sym? item) (search-symbol self file item stack) (list? item) (search-list self file item stack) (= :table (type item)) (search-table self file item stack) - (= 0 (length stack)) {:?definition item} ;; BASE CASE !! + (= 0 (length stack)) {:definition item} ;; BASE CASE !! (error (.. "I don't know what to do with " (view item)))))) (λ search-main [self file symbol] @@ -82,11 +83,11 @@ the data provided by compiler.fnl." (search-assignment self file ref stack) (_ def) (do - (if def.?keys - (fcollect [i (length def.?keys) 1 -1 &into stack] - (. def.?keys i))) - (search self file def.?definition stack)))) - ;; (search self file def.?definition stack)))) + (if def.keys + (fcollect [i (length def.keys) 1 -1 &into stack] + (. def.keys i))) + (search self file def.definition stack)))) + ;; (search self file def.definition stack)))) (λ past? [?ast byte] ;; check if a byte is past an ast object @@ -148,4 +149,5 @@ the data provided by compiler.fnl." {: find-symbol : search-main + : search-assignment : search} diff --git a/test/completion-test.fnl b/test/completion-test.fnl index 9c4869f..e4df905 100644 --- a/test/completion-test.fnl +++ b/test/completion-test.fnl @@ -69,7 +69,7 @@ (check-completion "(fn foo [arg1 arg2 arg3]\n )" 1 2 [:arg1 :arg2 :arg3])) (it "suggests function arguments at the top scope of the function" - (check-completion "(fn foo [arg1 arg2 arg3]\n (do (do (do ))))" 1 14 [:arg1 :arg2 :arg3]))) + (check-completion "(fn foo [arg1 arg2 arg3]\n (do (do (do ))))" 1 14 [:arg1 :arg2 :arg3])) ;; ;; Scope Ordering Rules ;; (it "does not suggest locals past the suggestion location when a symbol is partially typed") @@ -88,7 +88,14 @@ ;; (it "doesn't suggest macros in the middle of a list (open paren required)") ;; (it "doesn't suggest macros at the very top level") - ;; (it "suggests fields of tables") + (it "suggests fields of tables" + (check-completion + "(let [my-table {:foo 10 :bar 20}]\n my-table.)))" + 1 11 + [:foo :bar] + [:_G :local :doto :1]))) ;; no globals, specials, macros, or others + + ;; (it "suggests fields of strings")) ;; (it "suggests known fn fields of tables when using a method call multisym") ;; (it "suggests known fn keys when using the `:` special") ;; (it "suggests known keys when using the `.` special") diff --git a/test/hover-test.fnl b/test/hover-test.fnl index 928a76c..231beb2 100644 --- a/test/hover-test.fnl +++ b/test/hover-test.fnl @@ -41,4 +41,7 @@ (check "hover.fnl" 9 30 "```fnl\n:colon-string\n```")) (it "hovers over a literal nil" - (check "hover.fnl" 12 9 "```fnl\nnil\n```"))) + (check "hover.fnl" 12 9 "```fnl\nnil\n```")) + + (it "hovers over λ function" + (check "hover.fnl" 18 6 "```fnl\n(fn lambda-fn [arg1 arg2] ...)\n```\ndocstring"))) diff --git a/test/test-project/hover.fnl b/test/test-project/hover.fnl index 7039490..6070a3c 100644 --- a/test/test-project/hover.fnl +++ b/test/test-project/hover.fnl @@ -12,22 +12,9 @@ (local empty nil) (print empty) -(fn sd [] "short docstring" +(λ lambda-fn [arg1 arg2] + "docstring" + (print "body") nil) -(fn ld [arg1] - "long docstring -This function has a long docstring, and returns nil. -The docstring has newlines and markdown and stuff in it. - -```fnl -(ld 100 100) ;; ==> nil -``` - -@arg arg1 is ignored -@arg arg2 is ignored. -@returns nil" - (let [result nil] - result)) - -(ld (sd)) +(lambda-fn 1 2)