split fennel-ls.state into fennel-ls.config and fennel-ls.file
This commit is contained in:
parent
7194838132
commit
a68686da9f
@ -1,21 +1,23 @@
|
||||
(require :fennel)
|
||||
(local dispatch (require :fennel-ls.dispatch))
|
||||
(local json-rpc (require :fennel-ls.json-rpc))
|
||||
(local state (require :fennel-ls.state))
|
||||
(local lint (require :fennel-ls.lint))
|
||||
|
||||
(λ check [filenames]
|
||||
"non-interactive mode that gets executed from CLI with --check.
|
||||
runs lints on each file, then formats and prints them"
|
||||
(local files (require :fennel-ls.files))
|
||||
(local lint (require :fennel-ls.lint))
|
||||
(let [server (doto {}
|
||||
(dispatch.handle* {:id 1
|
||||
:jsonrpc "2.0"
|
||||
:method "initialize"
|
||||
:params {:capabilities {:general {:positionEncodings [:utf-8]}}
|
||||
:clientInfo {:name "fennel-ls"}
|
||||
; " don't think this is a valid URI, but I want to operate in the current directory
|
||||
; don't think this is a valid URI, but I want to operate in the current directory
|
||||
:rootUri "file://."}}))]
|
||||
(var should-err? false)
|
||||
(each [_ filename (ipairs filenames)]
|
||||
(let [file (state.get-by-uri server (.. "file://" filename))]
|
||||
(let [file (files.get-by-uri server (.. "file://" filename))]
|
||||
(lint.check server file)
|
||||
(each [_ {: message :range {: start}} (ipairs file.diagnostics)]
|
||||
(print (: "%s:%s:%s %s" :format filename
|
||||
|
||||
@ -1,81 +1,31 @@
|
||||
"State
|
||||
This module keeps track of the state of the language server:
|
||||
* Settings
|
||||
* Loaded files
|
||||
"Settings
|
||||
This module is in charge of setting up the default settings.
|
||||
|
||||
There is no global state in this project: all state is stored
|
||||
in the \"server\" object."
|
||||
Settings can be read without requiring this module: just look in `server.configuration`.
|
||||
There are no global settings. They're all stored in the `server` object.
|
||||
"
|
||||
|
||||
(local searcher (require :fennel-ls.searcher))
|
||||
(local utils (require :fennel-ls.utils))
|
||||
(local {: compile} (require :fennel-ls.compiler))
|
||||
|
||||
(λ read-file [server uri]
|
||||
(let [text (case (. server.preload uri)
|
||||
preload preload
|
||||
_ (let [file (io.open (utils.uri->path uri))]
|
||||
(if file
|
||||
(let [body (file:read :*a)]
|
||||
(file:close)
|
||||
body)
|
||||
(error (.. "failed to open file" uri)))))]
|
||||
{: uri : text}))
|
||||
|
||||
(λ get-by-uri [server uri]
|
||||
(or (. server.files uri)
|
||||
(let [file (read-file server uri)]
|
||||
(compile server file)
|
||||
(tset server.files uri file)
|
||||
file)))
|
||||
|
||||
(λ get-by-module [server module]
|
||||
;; check the cache
|
||||
(case (. server.modules module)
|
||||
uri
|
||||
(or (get-by-uri server uri)
|
||||
;; if the cached uri isn't found, clear the cache and try again
|
||||
(do (tset server.modules module nil)
|
||||
(get-by-module server module)))
|
||||
nil
|
||||
(case (searcher.lookup server module)
|
||||
uri
|
||||
(do
|
||||
(tset server.modules module uri)
|
||||
(get-by-uri server uri)))))
|
||||
|
||||
(λ set-uri-contents [server uri text]
|
||||
(case (. server.files uri)
|
||||
;; modify existing file
|
||||
file
|
||||
(do
|
||||
(when (not= text file.text)
|
||||
(set file.text text)
|
||||
(compile server file))
|
||||
file)
|
||||
|
||||
;; create new file
|
||||
nil
|
||||
(let [file {: uri : text}]
|
||||
(tset server.files uri file)
|
||||
(compile server file)
|
||||
file)))
|
||||
|
||||
(λ flush-uri [server uri]
|
||||
"get rid of data about a file, in case it changed in some way"
|
||||
(tset server.files uri nil))
|
||||
|
||||
;; TODO: set the warning levels of lints
|
||||
;; allow all globals
|
||||
;; pick from existing libraries of globals (ie love2d)
|
||||
;; pick between different versions of lua (ie luajit)
|
||||
;; pick a "compat always" mode that accepts anything if it could be valid in any lua
|
||||
;; 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
|
||||
;; TODO: Settings to set the warning levels of lints
|
||||
;; Setting to allow all globals
|
||||
;; Have an option for "union of all lua versions" lua version.
|
||||
;; Have an option for "intersection of all lua versions", ie disallow using (unpack) without saying (or table.unpack _G.unpack).
|
||||
|
||||
(local option-mt {})
|
||||
(fn option [default-value]
|
||||
"represents an \"option\" that the user can override"
|
||||
(doto [default-value] (setmetatable option-mt)))
|
||||
(fn option [default-value] (doto [default-value] (setmetatable option-mt)))
|
||||
|
||||
(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")
|
||||
:version (option "lua54")
|
||||
:checks {:unused-definition (option true)
|
||||
:unknown-module-field (option true)
|
||||
:unnecessary-method (option true)
|
||||
:bad-unpack (option true)
|
||||
:var-never-set (option true)
|
||||
:op-with-no-arguments (option true)
|
||||
:multival-in-middle-of-call (option true)}
|
||||
:native-libraries (option [])
|
||||
:extra-globals (option "")})
|
||||
|
||||
(fn make-configuration-from-template [default ?user ?parent]
|
||||
(if (= option-mt (getmetatable default))
|
||||
@ -93,20 +43,6 @@ in the \"server\" object."
|
||||
?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")
|
||||
:version (option "lua54")
|
||||
:checks {:unused-definition (option true)
|
||||
:unknown-module-field (option true)
|
||||
:unnecessary-method (option true)
|
||||
:bad-unpack (option true)
|
||||
:var-never-set (option true)
|
||||
:op-with-no-arguments (option true)
|
||||
:multival-in-middle-of-call (option true)}
|
||||
:native-libraries (option [])
|
||||
:extra-globals (option "")})
|
||||
|
||||
(λ make-configuration [?c]
|
||||
(make-configuration-from-template default-configuration ?c))
|
||||
|
||||
@ -126,7 +62,7 @@ However, when not an option, fennel-ls will fall back to positionEncoding=\"utf-
|
||||
:utf-8
|
||||
:utf-16)))
|
||||
|
||||
(λ init-state [server params]
|
||||
(λ initialize [server params]
|
||||
(set server.files {})
|
||||
(set server.preload {})
|
||||
(set server.modules {})
|
||||
@ -134,16 +70,12 @@ However, when not an option, fennel-ls will fall back to positionEncoding=\"utf-
|
||||
(set server.position-encoding (choose-position-encoding params))
|
||||
(set server.configuration (make-configuration (?. params :initializationOptions :fennel-ls)))
|
||||
;; Eglot does completions differently than every other client I've seen so far, in that it considers foo.bar to be one "symbol".
|
||||
;; If the user types `foo.b`, every other client accepts `bar` as a completion, bun eglot wants the full `foo.bar` multisym.
|
||||
;; If the user types `foo.b`, every other client accepts `bar` as a completion, bun eglot wants the full `foo.bar` symbol.
|
||||
(set server.EGLOT_COMPLETION_QUIRK_MODE (= (?. params :clientInfo :name) :Eglot)))
|
||||
|
||||
(λ write-configuration [server ?configuration]
|
||||
"This is where we can put anything that needs to react to config changes"
|
||||
(set server.configuration (make-configuration ?configuration)))
|
||||
|
||||
{: flush-uri
|
||||
: get-by-module
|
||||
: get-by-uri
|
||||
: init-state
|
||||
: set-uri-contents
|
||||
{: initialize
|
||||
: write-configuration}
|
||||
70
src/fennel-ls/files.fnl
Normal file
70
src/fennel-ls/files.fnl
Normal file
@ -0,0 +1,70 @@
|
||||
"State
|
||||
This module keeps track of the state of the language server:
|
||||
* Settings
|
||||
* Loaded files
|
||||
|
||||
There is no global state in this project: all state is stored
|
||||
in the \"server\" object."
|
||||
|
||||
(local searcher (require :fennel-ls.searcher))
|
||||
(local utils (require :fennel-ls.utils))
|
||||
(local {: compile} (require :fennel-ls.compiler))
|
||||
|
||||
(λ read-file [server uri]
|
||||
(let [text (case (. server.preload uri)
|
||||
preload preload
|
||||
_ (let [file (io.open (utils.uri->path uri))]
|
||||
(if file
|
||||
(let [body (file:read :*a)]
|
||||
(file:close)
|
||||
body)
|
||||
(error (.. "failed to open file" uri)))))]
|
||||
{: uri : text}))
|
||||
|
||||
(λ get-by-uri [server uri]
|
||||
(or (. server.files uri)
|
||||
(let [file (read-file server uri)]
|
||||
(compile server file)
|
||||
(tset server.files uri file)
|
||||
file)))
|
||||
|
||||
(λ get-by-module [server module]
|
||||
;; check the cache
|
||||
(case (. server.modules module)
|
||||
uri
|
||||
(or (get-by-uri server uri)
|
||||
;; if the cached uri isn't found, clear the cache and try again
|
||||
(do (tset server.modules module nil)
|
||||
(get-by-module server module)))
|
||||
nil
|
||||
(case (searcher.lookup server module)
|
||||
uri
|
||||
(do
|
||||
(tset server.modules module uri)
|
||||
(get-by-uri server uri)))))
|
||||
|
||||
(λ set-uri-contents [server uri text]
|
||||
(case (. server.files uri)
|
||||
;; modify existing file
|
||||
file
|
||||
(do
|
||||
(when (not= text file.text)
|
||||
(set file.text text)
|
||||
(compile server file))
|
||||
file)
|
||||
|
||||
;; create new file
|
||||
nil
|
||||
(let [file {: uri : text}]
|
||||
(tset server.files uri file)
|
||||
(compile server file)
|
||||
file)))
|
||||
|
||||
(λ flush-uri [server uri]
|
||||
"get rid of data about a file, in case it changed in some way"
|
||||
(tset server.files uri nil))
|
||||
|
||||
{: flush-uri
|
||||
: get-by-module
|
||||
: get-by-uri
|
||||
: set-uri-contents}
|
||||
@ -7,7 +7,8 @@ Every time the client sends a message, it gets handled by a function in the corr
|
||||
|
||||
(local lint (require :fennel-ls.lint))
|
||||
(local message (require :fennel-ls.message))
|
||||
(local state (require :fennel-ls.state))
|
||||
(local files (require :fennel-ls.files))
|
||||
(local config (require :fennel-ls.config))
|
||||
(local language (require :fennel-ls.language))
|
||||
(local formatter (require :fennel-ls.formatter))
|
||||
(local utils (require :fennel-ls.utils))
|
||||
@ -18,7 +19,7 @@ Every time the client sends a message, it gets handled by a function in the corr
|
||||
(local notifications [])
|
||||
|
||||
(λ requests.initialize [server send params]
|
||||
(state.init-state server params)
|
||||
(config.initialize server params)
|
||||
(let [capabilities
|
||||
{:positionEncoding server.position-encoding
|
||||
:textDocumentSync {:openClose true :change 2}
|
||||
@ -69,7 +70,7 @@ Every time the client sends a message, it gets handled by a function in the corr
|
||||
:serverInfo {:name "fennel-ls" :version "0.1.0"}}))
|
||||
|
||||
(λ requests.textDocument/definition [server send {: position :textDocument {: uri}}]
|
||||
(let [file (state.get-by-uri server uri)
|
||||
(let [file (files.get-by-uri server uri)
|
||||
byte (utils.position->byte file.text position server.position-encoding)]
|
||||
(case-try (language.find-symbol file.ast byte)
|
||||
(symbol [parent])
|
||||
@ -87,7 +88,7 @@ Every time the client sends a message, it gets handled by a function in the corr
|
||||
(λ requests.textDocument/references [server send {: position
|
||||
:textDocument {: uri}
|
||||
:context {:includeDeclaration ?include-declaration?}}]
|
||||
(let [file (state.get-by-uri server uri)
|
||||
(let [file (files.get-by-uri server uri)
|
||||
byte (utils.position->byte file.text position server.position-encoding)]
|
||||
(case-try (language.find-symbol file.ast byte)
|
||||
symbol
|
||||
@ -104,7 +105,7 @@ Every time the client sends a message, it gets handled by a function in the corr
|
||||
(catch _ nil))))
|
||||
|
||||
(λ requests.textDocument/hover [server send {: position :textDocument {: uri}}]
|
||||
(let [file (state.get-by-uri server uri)
|
||||
(let [file (files.get-by-uri server uri)
|
||||
byte (utils.position->byte file.text position server.position-encoding)]
|
||||
(case-try (language.find-symbol file.ast byte)
|
||||
symbol (language.search-main server file symbol {} {: byte})
|
||||
@ -183,7 +184,7 @@ Every time the client sends a message, it gets handled by a function in the corr
|
||||
_ nil)))
|
||||
|
||||
(λ requests.textDocument/completion [server send {: position :textDocument {: uri}}]
|
||||
(let [file (state.get-by-uri server uri)
|
||||
(let [file (files.get-by-uri server uri)
|
||||
byte (utils.position->byte file.text position server.position-encoding)
|
||||
(?symbol parents) (language.find-symbol file.ast byte)]
|
||||
(case (-?> ?symbol utils.multi-sym-split)
|
||||
@ -215,7 +216,7 @@ Every time the client sends a message, it gets handled by a function in the corr
|
||||
|
||||
|
||||
(λ requests.textDocument/rename [server send {: position :textDocument {: uri} :newName new-name}]
|
||||
(let [file (state.get-by-uri server uri)
|
||||
(let [file (files.get-by-uri server uri)
|
||||
byte (utils.position->byte file.text position server.position-encoding)]
|
||||
(case-try (language.find-symbol file.ast byte)
|
||||
symbol
|
||||
@ -253,7 +254,7 @@ Every time the client sends a message, it gets handled by a function in the corr
|
||||
(pos<= range-2.start range-1.end)))
|
||||
|
||||
(λ requests.textDocument/codeAction [server send {: range :textDocument {: uri} &as params}]
|
||||
(let [file (state.get-by-uri server uri)]
|
||||
(let [file (files.get-by-uri server uri)]
|
||||
(icollect [_ diagnostic (ipairs file.diagnostics)]
|
||||
(if (and (overlap? diagnostic.range range)
|
||||
diagnostic.quickfix)
|
||||
@ -261,13 +262,13 @@ Every time the client sends a message, it gets handled by a function in the corr
|
||||
:edit {:changes {uri (diagnostic.quickfix)}}}))))
|
||||
|
||||
(λ notifications.textDocument/didChange [server send {: contentChanges :textDocument {: uri}}]
|
||||
(local file (state.get-by-uri server uri))
|
||||
(state.set-uri-contents server uri (utils.apply-changes file.text contentChanges server.position-encoding))
|
||||
(local file (files.get-by-uri server uri))
|
||||
(files.set-uri-contents server uri (utils.apply-changes file.text contentChanges server.position-encoding))
|
||||
(lint.check server file)
|
||||
(send (message.diagnostics file)))
|
||||
|
||||
(λ notifications.textDocument/didOpen [server send {:textDocument {: languageId : text : uri}}]
|
||||
(local file (state.set-uri-contents server uri text))
|
||||
(local file (files.set-uri-contents server uri text))
|
||||
(lint.check server file)
|
||||
(send (message.diagnostics file))
|
||||
(set file.open? true))
|
||||
@ -277,14 +278,14 @@ Every time the client sends a message, it gets handled by a function in the corr
|
||||
(set fennel.macro-loaded []))
|
||||
|
||||
(λ notifications.textDocument/didClose [server send {:textDocument {: uri}}]
|
||||
(local file (state.get-by-uri server uri))
|
||||
(local file (files.get-by-uri server uri))
|
||||
(set file.open? false)
|
||||
(set fennel.macro-loaded [])
|
||||
;; TODO only reload from disk if we didn't get a didSave, instead of always
|
||||
(state.flush-uri server uri))
|
||||
(files.flush-uri server uri))
|
||||
|
||||
(λ notifications.workspace/didChangeConfiguration [server send {: settings}]
|
||||
(state.write-configuration server (?. settings :fennel-ls)))
|
||||
(config.write-configuration server (?. settings :fennel-ls)))
|
||||
|
||||
(λ requests.shutdown [server send]
|
||||
"The server still needs to respond to this request, so the program can't close yet. Just wait until notifications.exit"
|
||||
|
||||
@ -40,7 +40,7 @@ find the definition `10`, but if `opts.stop-early?` is set, it would find
|
||||
|
||||
(local {: sym? : list? : sequence? : varg?} (require :fennel))
|
||||
(local utils (require :fennel-ls.utils))
|
||||
(local state (require :fennel-ls.state))
|
||||
(local files (require :fennel-ls.files))
|
||||
(local docs (require :fennel-ls.docs))
|
||||
|
||||
(local get-ast-info utils.get-ast-info)
|
||||
@ -128,7 +128,7 @@ find the definition `10`, but if `opts.stop-early?` is set, it would find
|
||||
(let [mod (. call 2)]
|
||||
(if (= multival 1)
|
||||
(when (= :string (type mod))
|
||||
(let [newfile (state.get-by-module server mod)]
|
||||
(let [newfile (files.get-by-module server mod)]
|
||||
(when newfile
|
||||
(let [newitem (. newfile.ast (length newfile.ast))]
|
||||
(when (= (length stack) 1)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user