From 3c91ad068014b98e5510cd1815f9cf144897d16f Mon Sep 17 00:00:00 2001 From: XeroOl Date: Sat, 30 Jul 2022 01:30:01 -0500 Subject: [PATCH] More progress --- LICENSE | 20 +++++++++- Makefile | 11 ++--- fennel-ls.fnl | 89 ----------------------------------------- fls/error.fnl | 23 ----------- fls/file.fnl | 29 -------------- fls/init.fnl | 4 -- fls/log.fnl | 16 -------- fls/protocol.fnl | 58 --------------------------- main.fnl | 20 ---------- src/fennel-ls.fnl | 19 +++++++++ test.fnl | 5 --- test/document-test.fnl | 91 ++++++++++++++++++++++++++++++++++++++++++ test/file-test.fnl | 60 ---------------------------- test/init.fnl | 5 +++ test/json-rpc-test.fnl | 40 +++++++++++++++++++ test/lsp-test.fnl | 67 ++++++++++++++++++++----------- test/macros.fnl | 15 ++++--- test/protocol-test.fnl | 34 ---------------- 18 files changed, 234 insertions(+), 372 deletions(-) delete mode 100644 fennel-ls.fnl delete mode 100644 fls/error.fnl delete mode 100644 fls/file.fnl delete mode 100644 fls/init.fnl delete mode 100644 fls/log.fnl delete mode 100644 fls/protocol.fnl delete mode 100644 main.fnl create mode 100644 src/fennel-ls.fnl delete mode 100644 test.fnl create mode 100644 test/document-test.fnl delete mode 100644 test/file-test.fnl create mode 100644 test/init.fnl create mode 100644 test/json-rpc-test.fnl delete mode 100644 test/protocol-test.fnl diff --git a/LICENSE b/LICENSE index a22a2da..ce2d0f3 100644 --- a/LICENSE +++ b/LICENSE @@ -1 +1,19 @@ -MIT +Copyright (c) 2022 XeroOl + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Makefile b/Makefile index 58f3df8..a821468 100644 --- a/Makefile +++ b/Makefile @@ -1,13 +1,14 @@ -STATIC_LUA_LIB=/usr/lib/liblua.so.5.4 +LUA_LIB=/usr/lib/liblua.so.5.4 LUA_INCLUDE_PATH=$(shell lua5.4 -e 'print(package.cpath:match("[^;]+"))') -SOURCES=$(wildcard *.fnl) -SOURCES+=$(wildcard fls/*.fnl) +SOURCES=$(wildcard src/*.fnl) +SOURCES+=$(wildcard src/fennel-ls/*.fnl) .PHONY: test fennel-ls: $(SOURCES) - fennel --compile-binary main.fnl fennel-ls $(STATIC_LUA_LIB) $(LUA_INCLUDE_PATH) + FENNEL_PATH=src/?.fnl fennel --compile-binary src/fennel-ls.fnl fennel-ls $(LUA_LIB) $(LUA_INCLUDE_PATH) test: - fennel test.fnl + fennel --correlate test/init.fnl + diff --git a/fennel-ls.fnl b/fennel-ls.fnl deleted file mode 100644 index ead6262..0000000 --- a/fennel-ls.fnl +++ /dev/null @@ -1,89 +0,0 @@ -(local fennel (require :fennel)) -(local {: make-error-message} (require :fls.error)) - -(local requests []) -(local notifications []) - -(local capabilities - {:textDocumentSync 2 - :notebookDocumentSync nil - :completionProvider nil - :hoverProvider nil - :signatureHelpProvider nil - :declarationProvider nil - :definitionProvider nil - :typeDefinitionProvider nil - :implementationProvider nil - :referencesProvider nil - :documentHighlightProvider nil - :documentSymbolProvider nil - :codeActionProvider nil - :codeLensProvider nil - :documentLinkProvider nil - :colorProvider nil - :documentFormattingProvider nil - :documentRangeFormattingProvider nil - :documentOnTypeFormattingProvider nil - :renameProvider nil - :foldingRangeProvider nil - :executeCommandProvider nil - :selectionRangeProvider nil - :linkedEditingRangeProvider nil - :callHierarchyProvider nil - :semanticTokensProvider nil - :monikerProvider nil - :typeHierarchyProvider nil - :inlineValueProvider nil - :inlayHintProvider nil - :diagnosticProvider nil - :workspaceSymbolProvider nil}) -; :workspace {:workspaceFolders nil -; :fileOperations {:didCreate nil -; :willCreate nil -; :didRename nil -; :willRename nil -; :didDelete nil -; :willDelete nil}) - -(λ requests.initialize [self params] - {:capabilities capabilities - :serverInfo {:name "fennel-ls" :version "0.0.0"}}) - -(λ requests.shutdown [self]) - ;; Okay, I'll wait for the exit notification to actaully exit - -(λ notifications.exit [self] - (os.exit 0)) - -(λ run-request [self id method ?params] - (match (. requests method) - callback {:jsonrpc "2.0" - : id - :result (callback self ?params)} - nil (make-error-message - :MethodNotFound - (.. "\"" method "\" is not in the requests table") - id))) - -(λ run-response [self id result]) - ;; I don't care about responses yet - -(λ run-bad-response [self id err] - (error (.. "oopsie: " err.code))) - -(λ run-notification [self method ?params] - (match (. notifications method) - callback (callback self ?params) - nil nil)) ;; Silent error for unknown notifications - -(λ run [self msg] - "The entry point for all messages." - (match (values msg (type msg)) - {:jsonrpc "2.0" : id : method :params ?params} (run-request self id method ?params) - {:jsonrpc "2.0" : method :params ?params} (run-notification self method ?params) - {:jsonrpc "2.0" : id : result} (run-response self id result) - {:jsonrpc "2.0" : id :error err} (run-bad-response self id err) - (str :string) (make-error-message :ParseError str) - _ (make-error-message :BadMessage nil msg.id))) - -{: run} diff --git a/fls/error.fnl b/fls/error.fnl deleted file mode 100644 index 70ecaae..0000000 --- a/fls/error.fnl +++ /dev/null @@ -1,23 +0,0 @@ -(local error-codes - {;; JSON-RPC errors - :ParseError -32700 - :InvalidRequest -32600 - :MethodNotFound -32601 - :InvalidParams -32602 - :InternalError -32603 - ;; LSP errors - :ServerNotInitialized -32002 - :UnknownErrorCode -32001 - :RequestFailed -32802 ;; when the server has no excuse - :ServerCancelled -32802 - :ContentModified -32801 ;; I don't think this one is useful - :RequestCancelled -32800}) ;; I don't think I'm going to even support cancelling things, that sounds like a pain - -(λ make-error-message [code message ?id ?data] - {:jsonrpc "2.0" - :id ?id - :error {:code (or (. error-codes code) code) - :data ?data - : message}}) - -{: error-codes : make-error-message} diff --git a/fls/file.fnl b/fls/file.fnl deleted file mode 100644 index c50d4fa..0000000 --- a/fls/file.fnl +++ /dev/null @@ -1,29 +0,0 @@ -(local stringx (require :pl.stringx)) - -(λ open-uri [uri] - (local prefix "file://") - (assert (stringx.startswith uri prefix)) - (let [path (string.sub uri 8)] - (io.open path))) - -(λ make-file [uri lines] - {: uri - : lines}) - -(λ make-file-from-disk [uri] - (make-file - uri - (with-open [file (open-uri uri)] - (icollect [line (file:lines)] - line)))) - -(λ sub [self start-line start-char end-line end-char replacement] - (local index (+ 1 start-line)) - (tset self.lines index - (.. (string.sub (. self.lines index) 1 start-char) - replacement - (string.sub (. self.lines index) (+ 1 end-char))))) - -{: make-file - : make-file-from-disk - : sub} diff --git a/fls/init.fnl b/fls/init.fnl deleted file mode 100644 index d417c9c..0000000 --- a/fls/init.fnl +++ /dev/null @@ -1,4 +0,0 @@ -{:error (require :fls.error) - :log (require :fls.log) - :protocol (require :fls.protocol) - :file (require :fls.file)} diff --git a/fls/log.fnl b/fls/log.fnl deleted file mode 100644 index fd60ec0..0000000 --- a/fls/log.fnl +++ /dev/null @@ -1,16 +0,0 @@ -(local fennel (require :fennel)) -(local disable-logs false) -(if disable-logs - {:log #nil} - (let [logfile (io.open "/tmp/fennel.log" "w")] - (assert logfile) - (fn log [...] - (let [args []] - (for [i 1 (select :# ...)] - (table.insert args - (let [item (select i ...)] - (match (values item (type item)) - (str :string) str - ?any (fennel.view ?any))))) - (logfile:write (table.concat args) "\n"))) - {: log})) diff --git a/fls/protocol.fnl b/fls/protocol.fnl deleted file mode 100644 index bd37a3a..0000000 --- a/fls/protocol.fnl +++ /dev/null @@ -1,58 +0,0 @@ -" Language Server Protocol I/O - -This module implements the parsing and formatting needed to read/write messages using the language server protocol. -There are only two functions exposed here: - -* `read` receives a message from the client. -* `write` sends a message to the client." - -;; TODO find json library that doesn't conflate missing fields with null -(local {: encode : decode} (require :json.json)) -(local {: split} (require :pl.stringx)) - -(λ read-header [in ?header] - (let [header (or ?header {})] - (match (in:read) - "\r" header ;; hit an empty line, I'm done reading - nil nil ;; hit end of stream, return 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-n [in len ?buffer] - "read a string of exactly `len` characters from the `in` stream. -If there aren't enough bytes, return nil" - (local buffer (or ?buffer [])) - (if (<= len 0) - (table.concat buffer) - (match (in:read len) - content - (read-n in - (- len (length content)) - (doto buffer (table.insert content)))))) - -(λ read-content [in header] - (read-n in (tonumber header.Content-Length))) - -(λ read [in] - "Reads the next Language Server Protocol message from the given input stream" - (let [(_success? result) - (-?>> - (read-header in) - (read-content in) - (pcall decode))] - result)) - - -(λ write [out msg] - "Writes a Language Server Protocol message to the given output stream" - (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 - : write} diff --git a/main.fnl b/main.fnl deleted file mode 100644 index abec441..0000000 --- a/main.fnl +++ /dev/null @@ -1,20 +0,0 @@ -(local fennel (require :fennel)) -(local fls (require :fls)) -(local {: run} (require :fennel-ls)) - -(λ main-loop [in out] - (var state {}) - (while - (let [msg (fls.protocol.read in)] - (fls.log.log msg) - (-?>> msg - (run state) - (fls.protocol.write out)) - msg))) - -(λ main [] - (main-loop - (io.input) - (io.output))) - -(main) diff --git a/src/fennel-ls.fnl b/src/fennel-ls.fnl new file mode 100644 index 0000000..e85fd59 --- /dev/null +++ b/src/fennel-ls.fnl @@ -0,0 +1,19 @@ +(local fennel (require :fennel)) +(local dispatch (require :fennel-ls.dispatch)) +(local json-rpc (require :fennel-ls.json-rpc)) +(local {: log} (require :fennel-ls.log)) + +(λ main-loop [in out] + (local send #(json-rpc.write out $)) + (local state []) + (while true + (let [msg (json-rpc.read in)] + (log msg) + (dispatch.handle state send msg)))) + +(λ main [] + (main-loop + (io.input) + (io.output))) + +(main) diff --git a/test.fnl b/test.fnl deleted file mode 100644 index 6cc029a..0000000 --- a/test.fnl +++ /dev/null @@ -1,5 +0,0 @@ -((require :busted.runner)) - -(require :test.protocol-test) -(require :test.file-test) -(require :test.lsp-test) diff --git a/test/document-test.fnl b/test/document-test.fnl new file mode 100644 index 0000000..2585b97 --- /dev/null +++ b/test/document-test.fnl @@ -0,0 +1,91 @@ +(import-macros {: assert-matches : describe : it} :test.macros) +(local assert (require :luassert)) + +(local fennel (require :fennel)) +(local stringx (require :pl.stringx)) + +(local document (require :fennel-ls.document)) + +(local ROOT-URI + (.. "file://" + (-> (io.popen "pwd") + (: :read :*a) + (stringx.strip)))) + +(local FILE-URI (.. ROOT-URI "/test_document")) + +(describe "document" + (describe "create-from-disk" + (it "opens documents from disk" + (local uri (.. ROOT-URI "/test/init.fnl")) + + (local test-fnl-document (document.create-from-disk uri)) + (assert.equal (. test-fnl-document.lines 1) + "((require :busted.runner))")) + + (it "crashes on bad file" + (assert.errors #(document.create-from-disk "fill://my/path/here")) + (assert.errors #(document.create-from-disk "file:///this/path/hopefully/does/not/exist/on/the/host/system&^$!@#%")))) + + (describe "create-from-contents" + (it "opens documents from fixed contents" + (local uri (.. ROOT-URI "/test_document")) + (assert-matches + (document.create-from-contents uri "line 1\nline 2\nline 3") + {:lines ["line 1" "line 2" "line 3"]}))) + + (describe "sub" + (it "updates the start of a line" + (local my-document (document.create FILE-URI ["replace beginning"])) + (document.sub my-document 0 0 0 7 "the") + (assert-matches my-document {:lines ["the beginning"]})) + + (it "updates the end of a line" + (local my-document (document.create FILE-URI ["replace end"])) + (document.sub my-document 0 7 0 11 "ment") + (assert-matches my-document {:lines ["replacement"]})) + + (it "replaces a line" + (local my-document (document.create FILE-URI ["replace all"])) + (document.sub my-document 0 0 0 11 "new string") + (assert-matches my-document {:lines ["new string"]}))) + + ;; fixme: + ;; test for errors on out of bounds + ;; test for multiline edits + ;; test for unicode utf8 utf16 nightmare + + ;; (it "can handle unicode" + ;; (local uri (.. ROOT-URI "test_document")) + ;; (local my-document (document.create uri [""])) + ;; (document.sub my-document 0 0 0 0 "どれみふぁそらてぃど") + ;; (document.sub my-document 0 1 0 3 "😀") + ;; (document.sub my-document 0 11 0 11 "end") + ;; (assert-matches my-document {:lines ["ど😀ふぁそらてぃどend"]}))) + + (describe "apply-changes" + (it "can handle substituting things" + (local my-document (document.create FILE-URI ["replace beginning"])) + (document.apply-changes + my-document + [{:range {:start {:line 0 :character 0} + :end {:line 0 :character 7}} + :text "the"}]) + (assert-matches my-document {:lines ["the beginning"]})) + + (it "can handle replacing everything" + (local my-document (document.create FILE-URI ["this is the" "old file"])) + (document.apply-changes + my-document + [{:text "And this is the\nnew file"}]) + (assert-matches my-document {:lines ["And this is the" "new file"]}))) + + (describe "lines" + (it "splits lines" + (assert.same (document.lines "hello world\nnewlines are fun\rgaming\r\nworld +double double\r\n\ndouble trouble\n\r\nfunny\r\rand\n\nfinal") + ["hello world" "newlines are fun" + "gaming" "world" "double double" + "" "double trouble" + "" "funny" "" "and" + "" "final"])))) diff --git a/test/file-test.fnl b/test/file-test.fnl deleted file mode 100644 index 6b54bf3..0000000 --- a/test/file-test.fnl +++ /dev/null @@ -1,60 +0,0 @@ -(import-macros {: assert-matches : describe : it} :test.macros) -(local assert (require :luassert)) - -(local fennel (require :fennel)) -(local fls (require :fls)) -(local stringx (require :pl.stringx)) - -(local ROOT-URI - (.. "file://" - (-> (io.popen "pwd") - (: :read :*a) - (stringx.strip)) - "/")) - -(local FILE-URI (.. ROOT-URI "test_file")) - -(describe "File Loading" - (it "can open files from disk" - (local uri (.. ROOT-URI "test.fnl")) - - (local test-fnl-file (fls.file.make-file-from-disk uri)) - (assert.equal (. test-fnl-file.lines 1) - "((require :busted.runner))")) - - (it "can open files from fixed contents" - (local uri (.. ROOT-URI "test_file")) - (local my-file (fls.file.make-file uri ["line 1" "line 2" "line 3"])) - (assert - (match my-file - {:lines ["line 1" "line 2" "line 3"]} - true - otherwise (values false (fennel.view otherwise))))) - - (it "can update the start of a line" - (local my-file (fls.file.make-file FILE-URI ["replace beginning"])) - (fls.file.sub my-file 0 0 0 7 "the") - (assert-matches my-file {:lines ["the beginning"]})) - - (it "can update the end of a line" - (local my-file (fls.file.make-file FILE-URI ["replace end"])) - (fls.file.sub my-file 0 7 0 11 "ment") - (assert-matches my-file {:lines ["replacement"]})) - - (it "can replace a line" - (local my-file (fls.file.make-file FILE-URI ["replace all"])) - (fls.file.sub my-file 0 0 0 11 "new string") - (assert-matches my-file {:lines ["new string"]}))) - - ;; next steps: - ;; test for errors on out of bounds - ;; test for multiline edits - ;; test for unicode utf8 utf16 nightmare - - ;; (it "can handle unicode" - ;; (local uri (.. ROOT-URI "test_file")) - ;; (local my-file (fls.file.make-file uri [""])) - ;; (fls.file.sub my-file 0 0 0 0 "どれみふぁそらてぃど") - ;; (fls.file.sub my-file 0 1 0 3 "😀") - ;; (fls.file.sub my-file 0 11 0 11 "end") - ;; (assert-matches my-file {:lines ["ど😀ふぁそらてぃどend"]}))) diff --git a/test/init.fnl b/test/init.fnl new file mode 100644 index 0000000..f3ee0f4 --- /dev/null +++ b/test/init.fnl @@ -0,0 +1,5 @@ +((require :busted.runner)) +(tset (require :fennel) :path "./?.fnl;./src/?.fnl") +(require :test.json-rpc-test) +(require :test.document-test) +(require :test.lsp-test) diff --git a/test/json-rpc-test.fnl b/test/json-rpc-test.fnl new file mode 100644 index 0000000..1ef4ff0 --- /dev/null +++ b/test/json-rpc-test.fnl @@ -0,0 +1,40 @@ +(import-macros {: assert-matches : describe : it} :test.macros) +(local assert (require :luassert)) + +(local fennel (require :fennel)) +(local stringio (require :pl.stringio)) + +(local json-rpc (require :fennel-ls.json-rpc)) + +(describe "json-rpc" + (describe "read" + (it "parses incoming messages" + (let [out (stringio.open + "Content-Length: 29\r\n\r\n{\"my json content\":\"is cool\"}")] + (assert.same + {"my json content" "is cool"} + (json-rpc.read out)))) + + (it "can read multiple incoming 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 neat\"}")] + (assert.same + {"my json content" "is cool"} + (json-rpc.read out)) + (assert.same + {"my json content" "is neat"} + (json-rpc.read out)) + (assert.same + nil + (json-rpc.read out)))) + + (it "can report compiler errors" + (let [out (stringio.open "Content-Length: 9\r\n\r\n{{{{{}}}}")] + (assert (= (type (json-rpc.read out)) :string))))) + + (describe "write" + (it "serializes outgoing messages" + (let [in (stringio.create)] + (json-rpc.write in {"my json content" "is cool"}) + (assert.same "Content-Length: 29\r\n\r\n{\"my json content\":\"is cool\"}" + (in:value)))))) diff --git a/test/lsp-test.fnl b/test/lsp-test.fnl index 497a5c1..722c009 100644 --- a/test/lsp-test.fnl +++ b/test/lsp-test.fnl @@ -1,29 +1,50 @@ (import-macros {: assert-matches : describe : it} :test.macros) (local assert (require :luassert)) -(local {: run} (require :fennel-ls)) -(local fennel (require :fennel)) +(local dispatch (require :fennel-ls.dispatch)) +(local stringx (require :pl.stringx)) -(describe "initialization" - ;; TODO get rid of hardcoded paths here +(local ROOT-PATH + (-> (io.popen "pwd") + (: :read :*a) + (stringx.strip))) +(local ROOT-URI + (.. "document://" ROOT-PATH)) + +(local server-initialize-message + {:id 1 + :jsonrpc "2.0" + :method "initialize" + :params + {:capabilities {} + :clientInfo {:name "Neovim" :version "0.7.2"} + :initializationOptions {} + :processId 16245 + :rootPath ROOT-PATH + :rootUri ROOT-URI + :trace "off" + :workspaceFolders [{:name ROOT-PATH + :uri ROOT-URI}]}}) + +(describe "language server" (it "responds to initialize" - (local initialize - {:id 1 - :jsonrpc "2.0" - :method "initialize" - :params - {:capabilities {} - :clientInfo {:name "Neovim" :version "0.7.2"} - :initializationOptions {} - :processId 16245 - :rootPath "/home/xerool/Documents/projects/fennel-ls" - :rootUri "file:///home/xerool/Documents/projects/fennel-ls" - :trace "off" - :workspaceFolders [{:name "/home/xerool/Documents/projects/fennel-ls" - :uri "file:///home/xerool/Documents/projects/fennel-ls"}]}}) (assert-matches - (run [] initialize) - {:id 1 - :jsonrpc "2.0" - :result {:capabilities {} - :serverInfo {:name "fennel-ls" : version}}}))) + (dispatch.handle* [] server-initialize-message) + [{:id 1 + :jsonrpc "2.0" + :result {:capabilities {} + :serverInfo {:name "fennel-ls" : version}}}])) + + (it "can jump to definition" + (local state []) + (dispatch.handle* state server-initialize-message) + (assert-matches + (dispatch.handle* state + {:id 2 + :jsonrpc "2.0" + :method "textDocument/definition" + :params {:position {:character 5 :line 0} + :textDocument {:uri (.. ROOT-URI "/test.fnl")}}}) + [{:id 2 + :jsonrpc "2.0" + :result {: uri : range}}]))) ;; FIXME: test whether the location is correct diff --git a/test/macros.fnl b/test/macros.fnl index 495f71c..5875565 100644 --- a/test/macros.fnl +++ b/test/macros.fnl @@ -1,12 +1,17 @@ -(fn it [title ...] - `((. (require :busted) :it) - ,title (fn [] ,...))) +"This document does not include tests. Instead it includes macros that are used for tests." -(fn describe [title ...] +(fn it [desc ...] + "busted's `it` function" + `((. (require :busted) :it) + ,desc (fn [] ,desc ,...))) + +(fn describe [desc ...] + "busted's `describe` function" `((. (require :busted) :describe) - ,title (fn [] ,...))) + ,desc (fn [] ,desc ,...))) (fn assert-matches [item pattern] + "check if item matches a pattern according to fennel's `match` builtin" `(match ,item ,pattern nil ?otherwise# diff --git a/test/protocol-test.fnl b/test/protocol-test.fnl deleted file mode 100644 index 7559df6..0000000 --- a/test/protocol-test.fnl +++ /dev/null @@ -1,34 +0,0 @@ -(import-macros {: assert-matches : describe : it} :test.macros) -(local assert (require :luassert)) - -(local fennel (require :fennel)) -(local stringio (require :pl.stringio)) -(local fls (require :fls)) -(local {: run} (require :fennel-ls)) - -(describe "fls.protocol" - (it "parses incoming messages" - (let [out (stringio.open "Content-Length: 29\r\n\r\n{\"my json content\":\"is cool\"}")] - (assert.same {"my json content" "is cool"} - (fls.protocol.read out)))) - - (it "serializes outgoing messages" - (let [in (stringio.create)] - (fls.protocol.write in {"my json content" "is cool"}) - (assert.same "Content-Length: 29\r\n\r\n{\"my json content\":\"is cool\"}" - (in:value)))) - - (it "can read multiple incoming 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"} - (fls.protocol.read out)) - (assert.same {"my json content" "is cool"} - (fls.protocol.read out)) - (assert.same nil - (fls.protocol.read out)))) - - (it "can report the ParseError code" - (let [out (stringio.open "Content-Length: 9\r\n\r\n{{{{{}}}}")] - (assert-matches - (run [] (fls.protocol.read out)) - {:error {:code -32700} :jsonrpc "2.0"}))))