split fennel-ls.state into fennel-ls.config and fennel-ls.file

This commit is contained in:
XeroOl 2024-06-02 11:55:14 -05:00
parent 7194838132
commit a68686da9f
5 changed files with 120 additions and 115 deletions

View File

@ -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

View File

@ -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
View 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}

View File

@ -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"

View File

@ -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)