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
|
(local args
|
||||||
(case ast
|
(case ast
|
||||||
(where [_fn args] (fennel.sequence? args)) args
|
(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)]
|
(each [_ argument (ipairs args)]
|
||||||
(define (sym :nil) argument scope))) ;; we say function arguments are set to nil
|
(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)]
|
ast (icollect [ok ok-2 ast parser &until (not (and ok ok-2))] ast)]
|
||||||
;; compile
|
;; compile
|
||||||
(each [_i form (ipairs (if macro-file? (ast->macro-ast ast) ast))]
|
(each [_i form (ipairs (if macro-file? (ast->macro-ast ast) ast))]
|
||||||
(case (pcall fennel.compile form opts)
|
(case (xpcall #(fennel.compile form opts) fennel.traceback)
|
||||||
(where (or (nil err) (false err)) (not (err:find "__NOT_AN_ERROR\n?$")))
|
(where (or (nil err) (false err)) (not (err:find "^[^\n]-__NOT_AN_ERROR\n")))
|
||||||
(error (.. "\nyou have crashed the compiler with the message:" err
|
(error (.. "\nYou have crashed the fennel compiler or fennel-ls with the following message\n:" err
|
||||||
"\nI am considering supressing this error if I get a lot of false alarms"))))
|
"\n\n^^^ the error message above here is the root problem\n\n"))))
|
||||||
; (table.insert diagnostics
|
; (table.insert diagnostics
|
||||||
; {:range (message.pos->range 0 0 0 0)
|
; {:range (message.pos->range 0 0 0 0)
|
||||||
; :message (.. "unrecoverable compiler error: " err)})
|
; :message (.. "unrecoverable compiler error: " err)})
|
||||||
@ -233,7 +234,8 @@ later by fennel-ls.language to answer requests from the client."
|
|||||||
; ;; base case???
|
; ;; base case???
|
||||||
|
|
||||||
(each [sym definition (pairs definitions)]
|
(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)))
|
(not= "_" (: (tostring sym) :sub 1 1)))
|
||||||
(let [range (message.ast->range sym file)]
|
(let [range (message.ast->range sym file)]
|
||||||
(table.insert diagnostics
|
(table.insert diagnostics
|
||||||
@ -243,7 +245,6 @@ later by fennel-ls.language to answer requests from the client."
|
|||||||
:code 301
|
:code 301
|
||||||
:codeDescription "warning error"}))))
|
:codeDescription "warning error"}))))
|
||||||
|
|
||||||
|
|
||||||
(set file.ast ast)
|
(set file.ast ast)
|
||||||
(set file.scope scope)
|
(set file.scope scope)
|
||||||
(set file.scopes scopes)
|
(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
|
;; TODO only reload from disk if we didn't get a didSave, instead of always
|
||||||
(state.flush-uri self uri))
|
(state.flush-uri self uri))
|
||||||
|
|
||||||
(λ notifications.workspace/didChangeConfiguration [self send params]
|
(λ notifications.workspace/didChangeConfiguration [self send {: settings}]
|
||||||
(state.write-config self params.fennel-ls))
|
(state.write-configuration self settings.fennel-ls))
|
||||||
|
|
||||||
(λ requests.shutdown [self send]
|
(λ 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"
|
"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.insert result (join (utils.uri->path workspace) path)))))
|
||||||
(table.concat result ";")))
|
(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]))
|
(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])))
|
(fennel.searchModule mod (add-workspaces-to-path fennel-path [root-uri])))
|
||||||
modname (utils.path->uri modname)
|
modname (utils.path->uri modname)
|
||||||
|
|||||||
@ -60,11 +60,6 @@ object."
|
|||||||
"get rid of data about a file, in case it changed in some way"
|
"get rid of data about a file, in case it changed in some way"
|
||||||
(tset self.files uri nil))
|
(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
|
;; TODO: set the warning levels of lints
|
||||||
;; allow all globals
|
;; allow all globals
|
||||||
;; allow some globals
|
;; allow some globals
|
||||||
@ -74,32 +69,48 @@ object."
|
|||||||
;; make a "compat strict" mode that warns about any lua-version-specific patterns
|
;; 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
|
;; ie using (unpack) without saying (or table.unpack _G.unpack) or something like that
|
||||||
|
|
||||||
(λ write-config [self ?config]
|
(local option-mt {})
|
||||||
(if (not ?config)
|
(fn option [default-value]
|
||||||
(set self.config default-config) ;; fast path, use all defaults
|
"represents an \"option\" that the user can override"
|
||||||
(set self.config
|
(doto [default-value] (setmetatable option-mt)))
|
||||||
{;; fennel-path:
|
|
||||||
;; the path to use to find fennel files using (require) or (include)
|
(fn make-configuration-from-template [default ?user ?parent]
|
||||||
:fennel-path (or ?config.fennel-path
|
(if (= option-mt (getmetatable default))
|
||||||
default-config.fennel-path)
|
(let [setting
|
||||||
;; macro-path:
|
(match-try ?user
|
||||||
;; the path to use to find fennel files using (require-macros) or (include-macros)
|
nil (?. ?parent :all)
|
||||||
:macro-path (or ?config.macro-path
|
nil (. default 1))]
|
||||||
default-config.fennel-path)
|
(assert (= (type (. default 1)) (type setting)))
|
||||||
;; globals:
|
setting)
|
||||||
;; Comma separated list of extra globals that are allowed.
|
(= :table (type default))
|
||||||
:globals (or ?config.globals
|
(collect [k v (pairs default)]
|
||||||
default-config.globals)})))
|
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]
|
(λ init-state [self params]
|
||||||
(set self.files {})
|
(set self.files {})
|
||||||
(set self.modules {})
|
(set self.modules {})
|
||||||
(set self.root-uri params.rootUri)
|
(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
|
{: flush-uri
|
||||||
: get-by-module
|
: get-by-module
|
||||||
: get-by-uri
|
: get-by-uri
|
||||||
: init-state
|
: init-state
|
||||||
: set-uri-contents
|
: set-uri-contents
|
||||||
: write-config}
|
: write-configuration}
|
||||||
|
|||||||
@ -28,12 +28,14 @@
|
|||||||
diagnostic
|
diagnostic
|
||||||
(match responses
|
(match responses
|
||||||
[{:params {: diagnostics}}]
|
[{:params {: diagnostics}}]
|
||||||
(find [i v (ipairs diagnostics)]
|
(is (find [i v (ipairs diagnostics)]
|
||||||
(match v
|
(match v
|
||||||
{:message "tried to reference a special form without calling it"
|
{:message "tried to reference a special form without calling it"
|
||||||
:range {:start {:character 4 :line 0}
|
:range {:start {:character 4 :line 0}
|
||||||
:end {:character 6 :line 0}}}
|
:end {:character 6 :line 0}}}
|
||||||
v)))]
|
v))
|
||||||
|
"not found")
|
||||||
|
_ (error "did not match"))]
|
||||||
(is diagnostic "expected a diagnostic")))
|
(is diagnostic "expected a diagnostic")))
|
||||||
|
|
||||||
(it "handles parse errors"
|
(it "handles parse errors"
|
||||||
@ -42,12 +44,14 @@
|
|||||||
diagnostic
|
diagnostic
|
||||||
(match responses
|
(match responses
|
||||||
[{:params {: diagnostics}}]
|
[{:params {: diagnostics}}]
|
||||||
(find [i v (ipairs diagnostics)]
|
(is (find [i v (ipairs diagnostics)]
|
||||||
(match v
|
(match v
|
||||||
{:message "expected whitespace before opening delimiter ("
|
{:message "expected whitespace before opening delimiter ("
|
||||||
:range {:start {:character 17 :line 0}
|
:range {:start {:character 17 :line 0}
|
||||||
:end {:character 17 :line 0}}}
|
:end {:character 17 :line 0}}}
|
||||||
v)))]
|
v))
|
||||||
|
"not found")
|
||||||
|
_ (error "did not match"))]
|
||||||
(is diagnostic "expected a diagnostic")))
|
(is diagnostic "expected a diagnostic")))
|
||||||
|
|
||||||
(it "handles (match)"
|
(it "handles (match)"
|
||||||
@ -64,7 +68,21 @@
|
|||||||
(let [self (create-client)
|
(let [self (create-client)
|
||||||
responses (self:open-file! filename "(unknown-global-1 unknown-global-2)")]
|
responses (self:open-file! filename "(unknown-global-1 unknown-global-2)")]
|
||||||
(is-matching responses
|
(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:
|
;; TODO lints:
|
||||||
;; unnecessary (do) in body position
|
;; unnecessary (do) in body position
|
||||||
|
|||||||
@ -55,4 +55,7 @@
|
|||||||
(it "doesn't crash"
|
(it "doesn't crash"
|
||||||
(let [self (create-client)
|
(let [self (create-client)
|
||||||
state (require :fennel-ls.state)]
|
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
|
(if ?config
|
||||||
(dispatch.handle* self.server {:jsonrpc "2.0"
|
(dispatch.handle* self.server {:jsonrpc "2.0"
|
||||||
:method :workspace/didChangeConfiguration
|
:method :workspace/didChangeConfiguration
|
||||||
:params ?config}))
|
:params {:settings ?config}}))
|
||||||
self))
|
self))
|
||||||
|
|
||||||
(fn next-id! [self]
|
(fn next-id! [self]
|
||||||
|
|||||||
@ -1,39 +1,48 @@
|
|||||||
(import-macros {: is-matching : describe : it : before-each} :test)
|
(import-macros {: is-matching : describe : it : before-each} :test)
|
||||||
(local is (require :test.is))
|
(local is (require :test.is))
|
||||||
|
|
||||||
(local {: view} (require :fennel))
|
|
||||||
(local {: ROOT-URI
|
(local {: ROOT-URI
|
||||||
: create-client} (require :test.mock-client))
|
: create-client} (require :test.mock-client))
|
||||||
|
|
||||||
(local dispatch (require :fennel-ls.dispatch))
|
|
||||||
(local message (require :fennel-ls.message))
|
|
||||||
|
|
||||||
(describe "settings"
|
(describe "settings"
|
||||||
(it "can set the path"
|
(it "can set the path"
|
||||||
(let [client (doto (create-client {:fennel-ls {:fennel-path "./?/?.fnl"}})
|
(let [client (doto (create-client {:fennel-ls {:fennel-path "./?/?.fnl"}})
|
||||||
(: :open-file! (.. ROOT-URI :/test.fnl) "(local {: this-is-in-modname} (require :modname))"))
|
(: :open-file! (.. ROOT-URI :/test.fnl) "(local {: this-is-in-modname} (require :modname))"))
|
||||||
[{:result {:range message}}]
|
result (client:definition (.. ROOT-URI :/test.fnl) 0 12)]
|
||||||
(client:definition (.. ROOT-URI :/test.fnl) 0 12)]
|
(eval-compiler
|
||||||
(is.not.nil message)
|
(print "EVAL" (in-scope? (sym :message))))
|
||||||
"body")))
|
(is-matching
|
||||||
|
result
|
||||||
|
[{:result {:range message}}]
|
||||||
|
"error message")))
|
||||||
|
|
||||||
;; (it "can set the path"
|
;; (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"
|
;; (it "can infer the macro path from fennel-path"
|
||||||
;; (local self (doto [] (setup-server {:fennel-ls {:fennel-path "./?/?.fnl"}}))))
|
;; (local self (doto [] (setup-server {:fennel-ls {:fennel-path "./?/?.fnl"}}))))
|
||||||
|
|
||||||
;; (it "can accept an allowed global"
|
;; (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"
|
;; (it "can accept a list of allowed globals"
|
||||||
;; (local self (doto [] (setup-server {:fennel-ls {:globals "GAMESTATE,SCREEN_CENTER_X,ETC"}}))))
|
;; (local self (doto [] (setup-server {:fennel-ls {:extra-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]+"}}))))
|
|
||||||
|
|
||||||
;; (it "can turn off strict globals"
|
;; (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"
|
;; (it "can treat globals as a warning instead of an error"
|
||||||
;; (local self (doto [] (setup-server {:fennel-ls {:diagnostics {:E202 "warning"}}})))))
|
;; (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