Options over LSP
Fennel-ls now can receive options from the language client over the protocol. Future work still necessary to have project-local files.
This commit is contained in:
parent
f053a606a3
commit
fbfd7cdaaa
@ -122,7 +122,8 @@ later by fennel-ls.language to answer requests from the client."
|
||||
(local args
|
||||
(case ast
|
||||
(where [_fn args] (fennel.sequence? args)) args
|
||||
(where [_fn _name args] (fennel.sequence? args)) args))
|
||||
(where [_fn _name args] (fennel.sequence? args)) args
|
||||
_ []))
|
||||
(each [_ argument (ipairs args)]
|
||||
(define (sym :nil) argument scope))) ;; we say function arguments are set to nil
|
||||
|
||||
@ -213,10 +214,10 @@ later by fennel-ls.language to answer requests from the client."
|
||||
ast (icollect [ok ok-2 ast parser &until (not (and ok ok-2))] ast)]
|
||||
;; compile
|
||||
(each [_i form (ipairs (if macro-file? (ast->macro-ast ast) ast))]
|
||||
(case (pcall fennel.compile form opts)
|
||||
(where (or (nil err) (false err)) (not (err:find "__NOT_AN_ERROR\n?$")))
|
||||
(error (.. "\nyou have crashed the compiler with the message:" err
|
||||
"\nI am considering supressing this error if I get a lot of false alarms"))))
|
||||
(case (xpcall #(fennel.compile form opts) fennel.traceback)
|
||||
(where (or (nil err) (false err)) (not (err:find "^[^\n]-__NOT_AN_ERROR\n")))
|
||||
(error (.. "\nYou have crashed the fennel compiler or fennel-ls with the following message\n:" err
|
||||
"\n\n^^^ the error message above here is the root problem\n\n"))))
|
||||
; (table.insert diagnostics
|
||||
; {:range (message.pos->range 0 0 0 0)
|
||||
; :message (.. "unrecoverable compiler error: " err)})
|
||||
@ -233,7 +234,8 @@ later by fennel-ls.language to answer requests from the client."
|
||||
; ;; base case???
|
||||
|
||||
(each [sym definition (pairs definitions)]
|
||||
(if (and (= 0 (length definition.referenced-by))
|
||||
(if (and self.configuration.checks.unused-definition
|
||||
(= 0 (length definition.referenced-by))
|
||||
(not= "_" (: (tostring sym) :sub 1 1)))
|
||||
(let [range (message.ast->range sym file)]
|
||||
(table.insert diagnostics
|
||||
@ -243,7 +245,6 @@ later by fennel-ls.language to answer requests from the client."
|
||||
:code 301
|
||||
:codeDescription "warning error"}))))
|
||||
|
||||
|
||||
(set file.ast ast)
|
||||
(set file.scope scope)
|
||||
(set file.scopes scopes)
|
||||
|
||||
@ -180,8 +180,8 @@ Every time the client sends a message, it gets handled by a function in the corr
|
||||
;; TODO only reload from disk if we didn't get a didSave, instead of always
|
||||
(state.flush-uri self uri))
|
||||
|
||||
(λ notifications.workspace/didChangeConfiguration [self send params]
|
||||
(state.write-config self params.fennel-ls))
|
||||
(λ notifications.workspace/didChangeConfiguration [self send {: settings}]
|
||||
(state.write-configuration self settings.fennel-ls))
|
||||
|
||||
(λ requests.shutdown [self send]
|
||||
"The server still needs to respond to this request, so the program can't close yet. Just wait until notifications.exit"
|
||||
|
||||
@ -35,7 +35,7 @@ I suspect this file may be gone after a bit of refactoring."
|
||||
(table.insert result (join (utils.uri->path workspace) path)))))
|
||||
(table.concat result ";")))
|
||||
|
||||
(λ lookup [{:config {: fennel-path} : root-uri} mod]
|
||||
(λ lookup [{:configuration {: fennel-path} : root-uri} mod]
|
||||
(match (or ;; TODO support lua ;; (fennel.searchModule mod (add-workspaces-to-path luapath [root-uri]))
|
||||
(fennel.searchModule mod (add-workspaces-to-path fennel-path [root-uri])))
|
||||
modname (utils.path->uri modname)
|
||||
|
||||
@ -60,11 +60,6 @@ object."
|
||||
"get rid of data about a file, in case it changed in some way"
|
||||
(tset self.files uri nil))
|
||||
|
||||
(local default-config
|
||||
{:fennel-path "./?.fnl;./?/init.fnl;src/?.fnl;src/?/init.fnl"
|
||||
:macro-path "./?.fnl;./?/init-macros.fnl;./?/init.fnl;src/?.fnl;src/?/init-macros.fnl;src/?/init.fnl"
|
||||
:globals ""})
|
||||
|
||||
;; TODO: set the warning levels of lints
|
||||
;; allow all globals
|
||||
;; allow some globals
|
||||
@ -74,32 +69,48 @@ object."
|
||||
;; make a "compat strict" mode that warns about any lua-version-specific patterns
|
||||
;; ie using (unpack) without saying (or table.unpack _G.unpack) or something like that
|
||||
|
||||
(λ write-config [self ?config]
|
||||
(if (not ?config)
|
||||
(set self.config default-config) ;; fast path, use all defaults
|
||||
(set self.config
|
||||
{;; fennel-path:
|
||||
;; the path to use to find fennel files using (require) or (include)
|
||||
:fennel-path (or ?config.fennel-path
|
||||
default-config.fennel-path)
|
||||
;; macro-path:
|
||||
;; the path to use to find fennel files using (require-macros) or (include-macros)
|
||||
:macro-path (or ?config.macro-path
|
||||
default-config.fennel-path)
|
||||
;; globals:
|
||||
;; Comma separated list of extra globals that are allowed.
|
||||
:globals (or ?config.globals
|
||||
default-config.globals)})))
|
||||
(local option-mt {})
|
||||
(fn option [default-value]
|
||||
"represents an \"option\" that the user can override"
|
||||
(doto [default-value] (setmetatable option-mt)))
|
||||
|
||||
(fn make-configuration-from-template [default ?user ?parent]
|
||||
(if (= option-mt (getmetatable default))
|
||||
(let [setting
|
||||
(match-try ?user
|
||||
nil (?. ?parent :all)
|
||||
nil (. default 1))]
|
||||
(assert (= (type (. default 1)) (type setting)))
|
||||
setting)
|
||||
(= :table (type default))
|
||||
(collect [k v (pairs default)]
|
||||
k (make-configuration-from-template
|
||||
(. default k)
|
||||
(?. ?user k)
|
||||
?user))
|
||||
(error "This is a bug with fennel-ls: default-configuration has a key that isn't a table or option")))
|
||||
|
||||
(local default-configuration
|
||||
{:fennel-path (option "./?.fnl;./?/init.fnl;src/?.fnl;src/?/init.fnl")
|
||||
:macro-path (option "./?.fnl;./?/init-macros.fnl;./?/init.fnl;src/?.fnl;src/?/init-macros.fnl;src/?/init.fnl")
|
||||
:checks {:unused-definition (option true)}})
|
||||
|
||||
(λ make-configuration [?c]
|
||||
(make-configuration-from-template default-configuration ?c))
|
||||
|
||||
(λ init-state [self params]
|
||||
(set self.files {})
|
||||
(set self.modules {})
|
||||
(set self.root-uri params.rootUri)
|
||||
(write-config self))
|
||||
(set self.configuration (make-configuration)))
|
||||
|
||||
(λ write-configuration [self ?configuration]
|
||||
(set self.configuration (make-configuration ?configuration)))
|
||||
|
||||
|
||||
{: flush-uri
|
||||
: get-by-module
|
||||
: get-by-uri
|
||||
: init-state
|
||||
: set-uri-contents
|
||||
: write-config}
|
||||
: write-configuration}
|
||||
|
||||
@ -28,12 +28,14 @@
|
||||
diagnostic
|
||||
(match responses
|
||||
[{:params {: diagnostics}}]
|
||||
(find [i v (ipairs diagnostics)]
|
||||
(match v
|
||||
{:message "tried to reference a special form without calling it"
|
||||
:range {:start {:character 4 :line 0}
|
||||
:end {:character 6 :line 0}}}
|
||||
v)))]
|
||||
(is (find [i v (ipairs diagnostics)]
|
||||
(match v
|
||||
{:message "tried to reference a special form without calling it"
|
||||
:range {:start {:character 4 :line 0}
|
||||
:end {:character 6 :line 0}}}
|
||||
v))
|
||||
"not found")
|
||||
_ (error "did not match"))]
|
||||
(is diagnostic "expected a diagnostic")))
|
||||
|
||||
(it "handles parse errors"
|
||||
@ -42,12 +44,14 @@
|
||||
diagnostic
|
||||
(match responses
|
||||
[{:params {: diagnostics}}]
|
||||
(find [i v (ipairs diagnostics)]
|
||||
(match v
|
||||
{:message "expected whitespace before opening delimiter ("
|
||||
:range {:start {:character 17 :line 0}
|
||||
:end {:character 17 :line 0}}}
|
||||
v)))]
|
||||
(is (find [i v (ipairs diagnostics)]
|
||||
(match v
|
||||
{:message "expected whitespace before opening delimiter ("
|
||||
:range {:start {:character 17 :line 0}
|
||||
:end {:character 17 :line 0}}}
|
||||
v))
|
||||
"not found")
|
||||
_ (error "did not match"))]
|
||||
(is diagnostic "expected a diagnostic")))
|
||||
|
||||
(it "handles (match)"
|
||||
@ -64,7 +68,21 @@
|
||||
(let [self (create-client)
|
||||
responses (self:open-file! filename "(unknown-global-1 unknown-global-2)")]
|
||||
(is-matching responses
|
||||
[{:params {:diagnostics [a b]}}] "there should be a diagnostic for each one here"))))
|
||||
[{:params {:diagnostics [a b]}}] "there should be a diagnostic for each one here")))
|
||||
|
||||
(it "warns about unused variables"
|
||||
(let [self (create-client)
|
||||
responses (self:open-file! filename "(local x 10)")]
|
||||
(match responses
|
||||
[{:params {: diagnostics}}]
|
||||
(is (find [i v (ipairs diagnostics)]
|
||||
(match v
|
||||
{:message "unused definition: x"
|
||||
:range {:start {:character 7 :line 0}
|
||||
:end {:character 8 :line 0}}}
|
||||
v))
|
||||
"not found")
|
||||
_ (error "did not match")))))
|
||||
|
||||
;; TODO lints:
|
||||
;; unnecessary (do) in body position
|
||||
|
||||
@ -55,4 +55,7 @@
|
||||
(it "doesn't crash"
|
||||
(let [self (create-client)
|
||||
state (require :fennel-ls.state)]
|
||||
(state.get-by-module self.server "test.test-project.crash-files.test"))))
|
||||
(state.get-by-module self.server "test.test-project.crash-files.test")))
|
||||
(it "doesn't crash 2"
|
||||
(doto (create-client)
|
||||
(: :open-file! filename "(macro foo {})"))))
|
||||
|
||||
@ -31,7 +31,7 @@
|
||||
(if ?config
|
||||
(dispatch.handle* self.server {:jsonrpc "2.0"
|
||||
:method :workspace/didChangeConfiguration
|
||||
:params ?config}))
|
||||
:params {:settings ?config}}))
|
||||
self))
|
||||
|
||||
(fn next-id! [self]
|
||||
|
||||
@ -1,39 +1,48 @@
|
||||
(import-macros {: is-matching : describe : it : before-each} :test)
|
||||
(local is (require :test.is))
|
||||
|
||||
(local {: view} (require :fennel))
|
||||
(local {: ROOT-URI
|
||||
: create-client} (require :test.mock-client))
|
||||
|
||||
(local dispatch (require :fennel-ls.dispatch))
|
||||
(local message (require :fennel-ls.message))
|
||||
|
||||
(describe "settings"
|
||||
(it "can set the path"
|
||||
(let [client (doto (create-client {:fennel-ls {:fennel-path "./?/?.fnl"}})
|
||||
(: :open-file! (.. ROOT-URI :/test.fnl) "(local {: this-is-in-modname} (require :modname))"))
|
||||
[{:result {:range message}}]
|
||||
(client:definition (.. ROOT-URI :/test.fnl) 0 12)]
|
||||
(is.not.nil message)
|
||||
"body")))
|
||||
result (client:definition (.. ROOT-URI :/test.fnl) 0 12)]
|
||||
(eval-compiler
|
||||
(print "EVAL" (in-scope? (sym :message))))
|
||||
(is-matching
|
||||
result
|
||||
[{:result {:range message}}]
|
||||
"error message")))
|
||||
|
||||
;; (it "can set the path"
|
||||
;; (local self (doto [] (setup-server {:fennel-ls {:macro-path "./?/?.fnl"}}))))
|
||||
;; (let [client (doto (create-client {:fennel-ls {:macro-path "./?/?.fnl"}})
|
||||
;; (: :open-file! (.. ROOT-URI :/test.fnl) "(import-macros {: this-is-in-modname} :modname)"))]
|
||||
;; (is-matching
|
||||
;; (client:definition (.. ROOT-URI :/test.fnl) 0 12)
|
||||
;; [{:result {:range message}}]))))
|
||||
|
||||
;; (it "can infer the macro path from fennel-path"
|
||||
;; (local self (doto [] (setup-server {:fennel-ls {:fennel-path "./?/?.fnl"}}))))
|
||||
|
||||
;; (it "can accept an allowed global"
|
||||
;; (local self (doto [] (setup-server {:fennel-ls {:globals "vim"}}))))
|
||||
;; (local self (doto [] (setup-server {:fennel-ls {:extra-globals "vim"}}))))
|
||||
|
||||
;; (it "can accept a list of allowed globals"
|
||||
;; (local self (doto [] (setup-server {:fennel-ls {:globals "GAMESTATE,SCREEN_CENTER_X,ETC"}}))))
|
||||
|
||||
;; (it "can accept a way to allow all globals that match a pattern"
|
||||
;; (local self (doto [] (setup-server {:fennel-ls {:global-pattern "[A-Z]+"}}))))
|
||||
;; (local self (doto [] (setup-server {:fennel-ls {:extra-globals "GAMESTATE,SCREEN_CENTER_X,ETC"}}))))
|
||||
|
||||
;; (it "can turn off strict globals"
|
||||
;; (local self (doto [] (setup-server {:fennel-ls {:globals "*"}}))))
|
||||
;; (local self (doto [] (setup-server {:fennel-ls {:checks {:globals false}}}))))
|
||||
|
||||
;; (it "can treat globals as a warning instead of an error"
|
||||
;; (local self (doto [] (setup-server {:fennel-ls {:diagnostics {:E202 "warning"}}})))))
|
||||
|
||||
;; I suspect this test will fail when I put warnings for module return type
|
||||
(it "can disable some lints"
|
||||
(let [client (create-client {:fennel-ls {:checks {:unused-definition false}}})
|
||||
responses (client:open-file! (.. ROOT-URI :/test.fnl) "(local x 10)")]
|
||||
(is-matching responses
|
||||
[{:method :textDocument/publishDiagnostics
|
||||
:params {:diagnostics [nil]}}]
|
||||
"bad"))))
|
||||
|
||||
Loading…
Reference in New Issue
Block a user