Skip to content

Remove lsp4j on v1 branch #12

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 54 commits into from
Jul 15, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
607a2fe
First pass at removing lsp4j
mainej Jul 8, 2022
1dc0dac
Share liveness probe
mainej Jul 8, 2022
7101970
Properly handle character encoding
mainej Jul 8, 2022
548fd5e
Extract JSON-RPC message helpers
mainej Jul 8, 2022
f51c577
Thread context through handlers
mainej Jul 8, 2022
229def7
Allow nil responses
mainej Jul 8, 2022
82b34b0
Trace method
mainej Jul 9, 2022
c6cbc76
Avoid accidental conversion to map
mainej Jul 9, 2022
f94eceb
Simplify client usage of error codes
mainej Jul 9, 2022
cbf1610
Content-Length is in bytes not chars
mainej Jul 9, 2022
4d34f0f
Rename
mainej Jul 10, 2022
4b122fe
Record plans
mainej Jul 10, 2022
b20d0a5
Make requests cancellable
mainej Jul 10, 2022
6521c7f
Docs and comments
mainej Jul 10, 2022
ac6579b
Be more careful to only cancel request once
mainej Jul 10, 2022
59a9787
Try different approach to shutdown
mainej Jul 10, 2022
9567155
Only block 10 seconds for close server to finish processing previous …
mainej Jul 10, 2022
a037776
Preserve case of keys that are already strings
mainej Jul 11, 2022
dcd6e42
Don't add empty data to error messages
mainej Jul 11, 2022
6d8929a
Remove alpha namespaces
mainej Jul 11, 2022
e9d18c0
Remove deps on lsp4j
mainej Jul 11, 2022
170a651
Revert arg order
mainej Jul 12, 2022
ce960f9
Language servers don't have to require endpoint protocol
mainej Jul 12, 2022
63a8ce0
Rename and improve docs
mainej Jul 12, 2022
be9d336
Include id in tracing
mainej Jul 12, 2022
bd18fcf
Hold more data on pending-request object
mainej Jul 12, 2022
1eb7279
Close input on exit
mainej Jul 12, 2022
b2c3399
Respect back pressure from clients that are slow to read
mainej Jul 13, 2022
01b25d1
Read and write in threads, to avoid blocking go threads
mainej Jul 13, 2022
61a2dc7
Improve trace messages
mainej Jul 13, 2022
88a8200
Rename receiver/sender; move pipeline draining to shutdown
mainej Jul 13, 2022
b828d68
Add tests for server and request
mainej Jul 13, 2022
32fd7cd
Send traces to channel
mainej Jul 13, 2022
c81ca1c
Log more errors; send logs to channel
mainej Jul 14, 2022
9b46f29
Reduce knowledge of log channel format
mainej Jul 14, 2022
f0f0b58
Move the logger protocol back to clojure-lsp
mainej Jul 14, 2022
bef2b0f
Merge server and protocols subprojects
mainej Jul 14, 2022
0534c09
Conform message only once
mainej Jul 14, 2022
4f1d4a4
Handle parse error and invalid request the same
mainej Jul 14, 2022
bfe7cc1
Cutomize input keywordization function
mainej Jul 14, 2022
7f2be73
Close owned channels
mainej Jul 14, 2022
c206597
Fix comment
mainej Jul 14, 2022
255f1d2
Remove unused protocol method
mainej Jul 14, 2022
6996fdb
Remove parallelism
mainej Jul 14, 2022
9865b1b
Clean up and fix tests
mainej Jul 14, 2022
ac2712d
Discard stdout while running server code
mainej Jul 14, 2022
e48f55d
Clean up tests
mainej Jul 14, 2022
0d97376
Simplify let
mainej Jul 14, 2022
38779d3
Add comment
mainej Jul 14, 2022
5d1c339
Prep for 1.0.0 release
mainej Jul 14, 2022
772f669
Changelog
mainej Jul 14, 2022
786837e
Fix typo
mainej Jul 14, 2022
423a8a4
Document usage in README
mainej Jul 15, 2022
9aefcf6
Ignore $/cancelRequest notifications
mainej Jul 15, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .dir-locals.el
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
((clojure-mode
(cider-clojure-cli-aliases . "test")))
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@

## Unreleased

## v1.0.0

- Remove lsp4j, as per https://github.com/clojure-lsp/lsp4clj/issues/8
This is essentially a rewrite of lsp4clj. Users of lsp4clj v0.4.3 and earlier
are encouraged to upgrade. Bug fixes for these earlier versions will be
considered, but the lsp4j-based version of this library will not receive
long-term support. For an example of how to use the new version of the library,
see https://github.com/clojure-lsp/clojure-lsp/pull/1117

## v0.4.3

## v0.4.2
Expand Down
108 changes: 101 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,112 @@

A [Language Server Protocol](https://microsoft.github.io/language-server-protocol/) base for developing any LSP implementation in Clojure.

## Server
[![Clojars Project](https://img.shields.io/clojars/v/com.github.clojure-lsp/lsp4clj.svg)](https://clojars.org/com.github.clojure-lsp/lsp4clj)

[![Clojars Project](https://img.shields.io/clojars/v/com.github.clojure-lsp/lsp4clj-server.svg)](https://clojars.org/com.github.clojure-lsp/lsp4clj-server)
lsp4clj reads and writes from stdio, parsing JSON-RPC according to the LSP spec. It provides tools to allow server implementors to receive, process, and respond to any of the methods defined in the LSP spec, and to send their own requests and notifications to clients.

The lsp4clj-server has the necessary integration with Input/Output, LSP-JSON parsing, allowing for users of this lib to just code the entrypoints of each LSP method.
## Usage

## Protocols
### Create a server

[![Clojars Project](https://img.shields.io/clojars/v/com.github.clojure-lsp/lsp4clj-protocols.svg)](https://clojars.org/com.github.clojure-lsp/lsp4clj-protocols)
To initialize a server that will read from stdin and write to stdout:

The lsp4clj-protocols contains only the protocols/interfaces for servers that want to extend the official LSP protocol to provide more features, also, it is used by lsp4clj-server itself.
```clojure
(lsp4clj.server/stdio-server {:in System/in, :out System/out})
```

## Known LSPs users
The returned server will have a core.async `:log-ch`, from which you can read server logs (vectors beginning with a log level).

```clojure
(async/go-loop []
(when-let [[level & args] (async/<! (:log-ch server))]
(apply logger/warn level args)
(recur)))
```

### Receive messages

To receive messages from a client, lsp4clj defines a pair of multimethods, `lsp4clj.server/receive-notification` and `lsp4clj.server/receive-request` that dispatch on the method name (as defined by the LSP spec) of an incoming JSON-RPC message.

Server implementors should create `defmethod`s for the messages they want to process. (Other methods will be logged and responded to with a generic "Method not found" response.)

These defmethods receive 3 arguments, the method name, a "context", and the `params` of the [JSON-RPC request or notification object](https://www.jsonrpc.org/specification#request_object). The params will have been converted to kebab-case keywords. Read on for an explanation of what a "context" is and how to set it.

```clojure
;; a notification; return value is ignored
(defmethod lsp4clj.server/receive-notification "textDocument/didOpen" [_ context {:keys [text-document]}]
(handler/did-open context (:uri text-document) (:text text-document))

;; a request; return value is convert to a response
(defmethod lsp4clj.server/receive-request "textDocument/definition" [_ context params]
(->> params
(handler/definition context)
(conform-or-log ::coercer/location)))
```

The return value of requests will be converted to camelCase json and returned to the client. If the return value looks like `{:error ...}`, it is assumed to indicate an error response, and the `...` part will be set as the `error` of a [JSON-RPC error object](https://www.jsonrpc.org/specification#error_object). It is up to you to conform the `...` object (by giving it a code, message, and data.) Otherwise, the entire return value will be set as the `result` of a [JSON-RPC response object](https://www.jsonrpc.org/specification#response_object). (Message ids are handled internally by lsp4clj.)

### Send messages

Servers also initiate their own requests and notifications to a client. To send a notification, call `lsp4clj.server/send-notification`.

```clojure
(->> {:message message
:type type
:extra extra}
(conform-or-log ::coercer/show-message)
(lsp4clj.server/send-notification server "window/showMessage"))
```

Sending a request is similar, with `lsp4clj.server/send-request`. This method returns a request object which may be dereffed to get the client's response. Most of the time you will want to call `lsp4clj.server/deref-or-cancel`, which will send a `$/cancelRequest` to the client if a timeout is reached.

```clojure
(let [request (->> {:edit edit}
(conform-or-log ::coercer/workspace-edit-params)
(lsp4clj.server/send-request server "workspace/applyEdit"))
response (lsp4clj.server/deref-or-cancel request 10e3 ::timeout)]
(if (= ::timeout response)
(logger/error "No reponse from client after 10 seconds.")
response))
```

### Start and stop a server

The last step is to start the server you created earlier. Use `lsp4clj.server/start`. This method accepts two arguments, the server and a "context". Whatever you provide for the context will be passed into the notification and request `defmethods` you defined earlier. This is a convenient way to make components of your system available to those methods without definining global constants. Often the context will include the server itself so that you can initiate outbound requests and notifications in reaction to inbound messages.

```clojure
(lsp4clj.server/start server {:server server, :logger logger})
```

The return of `start` is a promise that will resolve when the server shuts down, which can happen in a few ways.

First, if the server's input is closed, it will shutdown too. Second, if you call `lsp4clj.server/shutdown` on it, it will shutdown.

When a server shuts down it stops reading input, finishes processing the messages it has in flight, and then closes is output. (It also closes its `:log-ch` and `:trace-ch`.) As such, it should probably not be shut down until the LSP notification `exit` (as opposed to the `shutdown` request) to ensure all messages are received.

## Development details

As you are implementing, you may want to trace incoming and outgoing messages. Initialize the server with `:trace? true` and then read traces (strings) off its `:trace-ch`.

```clojure
(let [server (lsp4clj.server/stdio-server {:trace? true
:in System/in
:out System/out})]
(async/go-loop []
(when-let [trace (async/<! (:trace-ch server))]
(logger/debug trace)
(recur)))
(lsp4clj.server/start server context))
```

For testing, observe that a client is in many ways like a server, in that it sends and receives requests and notifications. That is, LSP's flavor of JSON-RPC is bi-directional. As such, you may be able to use some of lsp4clj's tools to build a mock client for testing. See `integration.client` in `clojure-lsp` for one such example.

You may also find `lsp4clj.server/chan-server` a useful alternative to `stdio-server`. This server reads and writes off channels, insead of stdio streams.

## Caveats

You must not print to stdout while a server is running. This will corrupt its output stream and clients will receive malformed messages. To protect a block of code from writing to stdout, wrap it with `lsp4clj.server/discarding-stdout`. The `receive-notification` and `receive-request` multimethods are already protected this way, but tasks started outside of these multimethods need this protection added. See https://github.com/clojure-lsp/lsp4clj/issues/1 for future work on avoiding this problem.

## Known lsp4clj users

- [clojure-lsp](https://clojure-lsp.io/): A Clojure LSP server implementation.
17 changes: 6 additions & 11 deletions bb.edn
Original file line number Diff line number Diff line change
@@ -1,17 +1,13 @@
{:paths ["scripts"]
:min-bb-version "0.4.0"
:tasks {:requires ([babashka.fs :as fs])
clean (do (fs/delete-tree "server/target")
(fs/delete-tree "server/lsp4clj-server.jar")
(fs/delete-tree "protocols/target")
(fs/delete-tree "protocols/lsp4clj-protocols.jar"))
test (shell {:dir "server"} "clojure -M:test")
clean (do (fs/delete-tree "target")
(fs/delete-tree "lsp4clj.jar"))
test (shell "clojure -M:test")

server-pom (shell {:dir "server"} "clojure -T:build pom")
protocols-pom (shell {:dir "protocols"} "clojure -T:build pom")
pom (shell "clojure -T:build pom")

server-jar (shell {:dir "server"} "clojure -T:build jar")
protocols-jar (shell {:dir "protocols"} "clojure -T:build jar")
jar (shell "clojure -T:build jar")

lint (do (shell "clojure-lsp format --dry")
(shell "clojure-lsp clean-ns --dry")
Expand All @@ -22,5 +18,4 @@
;; Receive arg without v
tag lsp4clj.ci/tag

deploy-clojars-server (shell {:dir "server"} "clojure -T:build deploy-clojars")
deploy-clojars-protocols (shell {:dir "protocols"} "clojure -T:build deploy-clojars")}}
deploy-clojars (shell "clojure -T:build deploy-clojars")}}
2 changes: 1 addition & 1 deletion protocols/build.clj → build.clj
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
[clojure.string :as string]
[clojure.tools.build.api :as b]))

(def lib 'com.github.clojure-lsp/lsp4clj-protocols)
(def lib 'com.github.clojure-lsp/lsp4clj)
(def jar-file (format "target/%s.jar" (name lib)))

(def current-version (string/trim (slurp "resources/LSP4CLJ_VERSION")))
Expand Down
24 changes: 13 additions & 11 deletions deps.edn
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
{:paths []
:aliases {:dev {:extra-paths ["protocols/src"
"server/src"]
:extra-deps {lsp4clj/server {:local/root "server"}
lsp4clj/protocols {:local/root "protocols"}
io.github.clojure/tools.build {:git/url "https://github.com/clojure/tools.build.git"
:tag "v0.8.1"
:sha "7d40500"}}}
:test {:extra-deps {lambdaisland/kaocha {:mvn/version "1.64.1010"}}
:extra-paths ["server/test"]
:main-opts ["-m" "kaocha.runner"]}}}
{:deps {org.clojure/clojure {:mvn/version "1.11.1"}
org.clojure/core.async {:mvn/version "1.5.648"}
camel-snake-kebab/camel-snake-kebab {:mvn/version "0.4.3"}
cheshire/cheshire {:mvn/version "5.11.0"} }
:paths ["src" "resources"]
:aliases {:test {:extra-deps {lambdaisland/kaocha {:mvn/version "1.64.1010"}}
:extra-paths ["test"]
:main-opts ["-m" "kaocha.runner"]}
:build {:extra-paths ["resources"]
:deps {io.github.clojure/tools.build {:git/tag "v0.8.1"
:git/sha "7d40500"}
slipset/deps-deploy {:mvn/version "0.2.0"}}
:ns-default build}}}
5 changes: 5 additions & 0 deletions pom.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Generated by org.clojure/tools.build
# Thu Jul 14 13:48:53 PDT 2022
version=1.0.0
groupId=com.github.clojure-lsp
artifactId=lsp4clj
35 changes: 25 additions & 10 deletions protocols/pom.xml → pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,37 @@
<modelVersion>4.0.0</modelVersion>
<packaging>jar</packaging>
<groupId>com.github.clojure-lsp</groupId>
<artifactId>lsp4clj-protocols</artifactId>
<version>0.4.3</version>
<name>lsp4clj-protocols</name>
<artifactId>lsp4clj</artifactId>
<version>1.0.0</version>
<name>lsp4clj</name>
<dependencies>
<dependency>
<groupId>org.clojure</groupId>
<artifactId>clojure</artifactId>
<version>1.11.1</version>
</dependency>
<dependency>
<groupId>cheshire</groupId>
<artifactId>cheshire</artifactId>
<version>5.11.0</version>
</dependency>
<dependency>
<groupId>camel-snake-kebab</groupId>
<artifactId>camel-snake-kebab</artifactId>
<version>0.4.3</version>
</dependency>
<dependency>
<groupId>org.clojure</groupId>
<artifactId>core.async</artifactId>
<version>1.5.648</version>
</dependency>
</dependencies>
<repositories>
<repository>
<id>clojars</id>
<url>https://repo.clojars.org/</url>
</repository>
</repositories>
<build>
<sourceDirectory>src</sourceDirectory>
<resources>
Expand All @@ -21,16 +42,10 @@
</resource>
</resources>
</build>
<repositories>
<repository>
<id>clojars</id>
<url>https://repo.clojars.org/</url>
</repository>
</repositories>
<scm>
<url>https://github.com/clojure-lsp/lsp4clj</url>
<connection>scm:git:git://github.com/clojure-lsp/lsp4clj.git</connection>
<developerConnection>scm:git:ssh://git@github.com/clojure-lsp/lsp4clj.git</developerConnection>
<tag>0.4.3</tag>
<tag>v1.0.0</tag>
</scm>
</project>
7 changes: 0 additions & 7 deletions protocols/deps.edn

This file was deleted.

1 change: 0 additions & 1 deletion protocols/resources

This file was deleted.

13 changes: 0 additions & 13 deletions protocols/src/lsp4clj/components.clj

This file was deleted.

41 changes: 0 additions & 41 deletions protocols/src/lsp4clj/protocols/feature_handler.clj

This file was deleted.

52 changes: 0 additions & 52 deletions protocols/src/lsp4clj/protocols/logger.clj

This file was deleted.

Loading