From a68686da9fabcc24dde7a94dd00741a15d683c98 Mon Sep 17 00:00:00 2001 From: XeroOl Date: Sun, 2 Jun 2024 11:55:14 -0500 Subject: [PATCH] split fennel-ls.state into fennel-ls.config and fennel-ls.file --- src/fennel-ls.fnl | 10 +- src/fennel-ls/{state.fnl => config.fnl} | 122 ++++++------------------ src/fennel-ls/files.fnl | 70 ++++++++++++++ src/fennel-ls/handlers.fnl | 29 +++--- src/fennel-ls/language.fnl | 4 +- 5 files changed, 120 insertions(+), 115 deletions(-) rename src/fennel-ls/{state.fnl => config.fnl} (52%) create mode 100644 src/fennel-ls/files.fnl diff --git a/src/fennel-ls.fnl b/src/fennel-ls.fnl index 27326b4..4629fe6 100644 --- a/src/fennel-ls.fnl +++ b/src/fennel-ls.fnl @@ -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 diff --git a/src/fennel-ls/state.fnl b/src/fennel-ls/config.fnl similarity index 52% rename from src/fennel-ls/state.fnl rename to src/fennel-ls/config.fnl index 99bfbe8..b415f5c 100644 --- a/src/fennel-ls/state.fnl +++ b/src/fennel-ls/config.fnl @@ -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} diff --git a/src/fennel-ls/files.fnl b/src/fennel-ls/files.fnl new file mode 100644 index 0000000..59524f6 --- /dev/null +++ b/src/fennel-ls/files.fnl @@ -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} diff --git a/src/fennel-ls/handlers.fnl b/src/fennel-ls/handlers.fnl index 92b369d..03c7177 100644 --- a/src/fennel-ls/handlers.fnl +++ b/src/fennel-ls/handlers.fnl @@ -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" diff --git a/src/fennel-ls/language.fnl b/src/fennel-ls/language.fnl index 9759609..835d454 100644 --- a/src/fennel-ls/language.fnl +++ b/src/fennel-ls/language.fnl @@ -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)