It's now working all the way, but supports nothing

This commit is contained in:
XeroOl 2022-07-24 22:36:42 -05:00
parent 5deb5e1ed0
commit f7b7b64d38
No known key found for this signature in database
GPG Key ID: 9DD4B4B4DAED0322
4 changed files with 162 additions and 163 deletions

View File

@ -1,86 +1,87 @@
(local {: encode : decode} (require :json.json)) (local requests [])
(local {: split} (require :pl.stringx)) (local notifications [])
(local capabilities {:positionEncoding nil ; "utf-8" (local capabilities
:textDocumentSync nil {:positionEncoding "utf-8"
:notepookDocumentSync nil :textDocumentSync nil
:completionProvider nil :notebookDocumentSync nil
:hoverProvider nil :completionProvider nil
:signatureHelpProvider nil :hoverProvider nil
:declarationProvider nil :signatureHelpProvider nil
:definitionProvider nil :declarationProvider nil
:typeDefinitionProvider nil :definitionProvider nil
:implementationProvider nil :typeDefinitionProvider nil
:referencesProvider nil :implementationProvider nil
:documentHighlightProvider nil :referencesProvider nil
:documentSymbolProvider nil :documentHighlightProvider nil
:codeActionProvider nil :documentSymbolProvider nil
:codeLensProvider nil :codeActionProvider nil
:documentLinkProvider nil :codeLensProvider nil
:colorProvider nil :documentLinkProvider nil
:documentFormattingProvider nil :colorProvider nil
:documentRangeFormattingProvider nil :documentFormattingProvider nil
:documentOnTypeFormattingProvider nil :documentRangeFormattingProvider nil
:renameProvider nil :documentOnTypeFormattingProvider nil
:foldingRangeProvider nil :renameProvider nil
:executeCommandProvider nil :foldingRangeProvider nil
:selectionRangeProvider nil :executeCommandProvider nil
:linkedEditingRangeProvider nil :selectionRangeProvider nil
:callHierarchyProvider nil :linkedEditingRangeProvider nil
:semanticTokensProvider nil :callHierarchyProvider nil
:monikerProvider nil :semanticTokensProvider nil
:typeHierarchyProvider nil :monikerProvider nil
:inlineValueProvider nil :typeHierarchyProvider nil
:inlayHintProvider nil :inlineValueProvider nil
:diagnosticProvider nil :inlayHintProvider nil
:workspaceSymbolProvider nil :diagnosticProvider nil
:workspace nil}) :workspaceSymbolProvider nil
:workspace {:workspaceFolders nil
:fileOperations {:didCreate nil
:willCreate nil
:didRename nil
:willRename nil
:didDelete nil
:willDelete nil}}})
(local handlers {}) (λ requests.initialize [params]
{:capabilities capabilities
:serverInfo {:name "fennel-ls" :version "0.0.0"}})
(λ handlers.initialize [params] (λ requests.shutdown [])
{:capabilities capabilities ;; no op
:serverInfo {:name "fennel-ls" :version "0.0.0"}})
(λ receive-message [in] (λ notifications.exit []
(local header {}) (os.exit 0))
(while
(match (in:read)
"\r" false
header-line
(let [[k v] (split header-line ": " 2)]
(tset header k v)
true)
_ nil))
(let [len (tonumber header.Content-Length)
buffer []]
(var sofar 0)
(while (< sofar len)
(let [r (in:read (- len sofar))]
(set sofar (+ sofar (length r)))
(table.insert buffer r)))
(decode (table.concat buffer))))
(λ send-message [out msg] (λ handle-request [id method ?params]
(let [content (encode msg) (let [callback (. requests method)
msg-stringified (.. "Content-Length: " (length content) "\r\n\r\n" content)] result {: id :jsonrpc "2.0"}]
(out:write msg-stringified))) (if callback
(tset result :result (callback ?params))
(tset result :error (.. "Unknown message type: " method)))
result))
(λ handle [{: jsonrpc : method : params : id &as msg}] (λ handle-response [id result])
(assert (= jsonrpc "2.0")) ;; Do nothing
;; Right now, if the callback crashes, the whole server does.
;; It would be nice to turn this into an error message
(match (. handlers method)
callback
(let [result (callback params)]
{: id
: method
:params result
:jsonrpc "2.0"})
_ {: id
: method
:error (.. "unknown method " msg.method)}))
{: receive-message (λ handle-bad-response [id err]
: send-message (error (.. "oopsie: " err.code)))
: handle}
(λ handle-notification [method ?params]
(let [callback (. notifications method)]
(if callback
(callback ?params))))
(λ handle [msg]
"The entry point for all messages."
(assert (= msg.jsonrpc "2.0") "Aha! You forgot to repeat that jsonrpc is version 2.0!")
(match msg
{: id : method :params ?params} (handle-request id method ?params)
{: method :params ?params} (handle-notification method ?params)
{: id : result} (handle-response id result)
{: id :error err} (handle-bad-response id err)
_ {:id msg.id
:error "I just received a message that doesn't make sense to me"
:jsonrpc "2.0"}))
{: handle}

44
lsp-io.fnl Normal file
View File

@ -0,0 +1,44 @@
(local {: encode : decode} (require :json.json))
(local {: split} (require :pl.stringx))
(λ read-header [in ?header]
(let [header (or ?header {})]
(match (in:read)
;; base cases
"\r" header
nil nil
;; reading an actual line
header-line
(let [[k v] (split header-line ": " 2)]
(tset header k (string.sub v 1 -2))
(read-header in header)))))
(λ read-content [in header]
(let [len (tonumber header.Content-Length)
buffer []]
;; TODO make this code as tolerable as possible
(var sofar 0)
(var currently-read-bytes nil)
(while
(and (< sofar len)
(do (set currently-read-bytes (in:read (- len sofar)))
currently-read-bytes))
(set sofar (+ sofar (length currently-read-bytes)))
(table.insert buffer currently-read-bytes))
(decode (table.concat buffer))))
(λ read-message [in]
(match (read-header in)
header (read-content in header)
nil nil))
(λ write-message [out msg]
(let [content (encode msg)
msg-stringified (.. "Content-Length: " (length content) "\r\n\r\n" content)]
(out:write msg-stringified)
(when out.flush
(out:flush))))
{: read-message
: write-message}

View File

@ -1,20 +1,16 @@
(local (local fennel (require :fennel))
{: handle (local {: read-message : write-message} (require :lsp-io))
: send-message (local {: handle} (require :fennel-ls))
: receive-message}
(require "fennel-ls")) (λ main-loop [in out]
(let [msg (read-message in)]
(when msg
(let [response (handle msg)]
(when response
(write-message out response))
(main-loop in out)))))
(λ main [] (λ main []
(local in (io.input)) (main-loop (io.input) (io.output)))
(local out (io.output))
(while
(match (receive-message in)
msg
(do
(let [response (handle msg)]
(if msg.id
(send-message out response)))
true)
_ nil)))
(main) (main)

View File

@ -1,93 +1,43 @@
(local fennel-ls (require :fennel-ls)) (local {: handle} (require :fennel-ls))
(local {: read-message : write-message} (require :lsp-io))
(local stringio (require :pl.stringio)) (local stringio (require :pl.stringio))
(local {: view} (require :fennel)) (local {: view} (require :fennel))
(local busted (require :busted)) (local busted (require :busted))
((require :busted.runner)) ((require :busted.runner))
(macro describe! [title ...] (macro it! [title ...] `(busted.it ,title (fn [] ,...)))
`(busted.describe ,title (fn [] ,...))) (macro describe! [title ...] `(busted.describe ,title (fn [] ,...)))
(macro it! [title ...]
`(busted.it ,title (fn [] ,...)))
(describe! "fennel-ls" (describe! "fennel-ls"
(it! "parses incoming messages" (it! "parses incoming messages"
(let [out (stringio.open "Content-Length: 29\r\n\r\n{\"my json content\":\"is cool\"}")] (let [out (stringio.open "Content-Length: 29\r\n\r\n{\"my json content\":\"is cool\"}")]
(assert.same {"my json content" "is cool"} (assert.same {"my json content" "is cool"}
(fennel-ls.recieve-message out)))) (read-message out))))
(it! "serializes outgoing messages" (it! "serializes outgoing messages"
(let [in (stringio.create)] (let [in (stringio.create)]
(fennel-ls.send-message in {"my json content" "is cool"}) (write-message in {"my json content" "is cool"})
(assert.same "Content-Length: 29\r\n\r\n{\"my json content\":\"is cool\"}" (assert.same "Content-Length: 29\r\n\r\n{\"my json content\":\"is cool\"}"
(in:value)))) (in:value))))
(it! "can read multiple messages"
(let [out (stringio.open "Content-Length: 29\r\n\r\n{\"my json content\":\"is cool\"}Content-Length: 29\r\n\r\n{\"my json content\":\"is cool\"}")]
(assert.same {"my json content" "is cool"}
(read-message out))
(assert.same {"my json content" "is cool"}
(read-message out))
(assert.same nil
(read-message out))))
(it! "responds to initialize" (it! "responds to initialize"
(local initialize-message (local initialize-message
{:id 1 {:id 1
:jsonrpc "2.0" :jsonrpc "2.0"
:method "initialize" :method "initialize"
:params :params
{:capabilities {:callHierarchy {:dynamicRegistration false} {:capabilities {}
:textDocument {:codeAction {:codeActionLiteralSupport
{:codeActionKind
{:valueSet
["" "Empty" "QuickFix" "Refactor"
"RefactorExtract" "RefactorInline"
"RefactorRewrite" "Source"
"SourceOrganizeImports" "quickfix"
"refactor" "refactor.extract"
"refactor.inline" "refactor.rewrite"
"source" "source.organizeImports"]}}
:dataSupport true
:dynamicRegistration false
:resolveSupport {:properties ["edit"]}}
:completion {:completionItem {:commitCharactersSupport true
:deprecatedSupport true
:documentationFormat ["markdown" "plaintext"]
:insertReplaceSupport true
:labelDetailsSupport true
:preselectSupport true
:resolveSupport {:properties ["documentation" "detail" "additionalTextEdits"]}
:snippetSupport true
:tagSupport {:valueSet [1]}}
:completionItemKind {:valueSet [1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25]}
:contextSupport false
:dynamicRegistration false}
:declaration {:linkSupport true}
:definition {:linkSupport true}
:documentHighlight {:dynamicRegistration false}
:documentSymbol {:dynamicRegistration false
:hierarchicalDocumentSymbolSupport true
:symbolKind {:valueSet [1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26]}}
:hover {:contentFormat ["markdown" "plaintext"]
:dynamicRegistration false}
:implementation {:linkSupport true}
:publishDiagnostics {:relatedInformation true
:tagSupport {:valueSet [1 2]}}
:references {:dynamicRegistration false}
:rename {:dynamicRegistration false
:prepareSupport true}
:signatureHelp {:dynamicRegistration false
:signatureInformation {:activeParameterSupport true
:documentationFormat ["markdown" "plaintext"]
:parameterInformation {:labelOffsetSupport true}}}
:synchronization {:didSave true
:dynamicRegistration false
:willSave false
:willSaveWaitUntil false}
:typeDefinition {:linkSupport true}}
:window {:showDocument {:support false}
:showMessage {:messageActionItem {:additionalPropertiesSupport false}}
:workDoneProgress true}
:workspace {:applyEdit true
:configuration true
:symbol {:dynamicRegistration false
:hierarchicalWorkspaceSymbolSupport true
:symbolKind {:valueSet [1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26]}}
:workspaceEdit {:resourceOperations ["rename" "create" "delete"]}
:workspaceFolders true}}
:clientInfo {:name "Neovim" :version "0.7.2"} :clientInfo {:name "Neovim" :version "0.7.2"}
:initializationOptions {} :initializationOptions {}
:processId 16245 :processId 16245
@ -96,4 +46,12 @@
:trace "off" :trace "off"
:workspaceFolders [{:name "/home/xerool/Documents/projects/fennel-ls" :workspaceFolders [{:name "/home/xerool/Documents/projects/fennel-ls"
:uri "file:///home/xerool/Documents/projects/fennel-ls"}]}}) :uri "file:///home/xerool/Documents/projects/fennel-ls"}]}})
(local result (fennel-ls.handle initialize-message)))) (assert
(match (handle initialize-message)
{:id 1
:jsonrpc "2.0"
:result {:capabilities {}
:serverInfo {:name "fennel-ls" : version}}}
true
otherwise (values false (view otherwise))))))