generated from redhat-developer/new-project-template
-
Notifications
You must be signed in to change notification settings - Fork 28
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fixes #24 Signed-off-by: azerr <[email protected]>
- Loading branch information
1 parent
7b5309e
commit 929a10b
Showing
16 changed files
with
498 additions
and
11 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,244 @@ | ||
# Developer guide | ||
|
||
This section explains step by step how to add your own LSP language server in your IntelliJ plugin. | ||
|
||
## Reference LSP4IJ | ||
|
||
### plugin.xml | ||
|
||
The first step is to reference LSP4IJ. LSP4IJ uses `com.redhat.devtools.lsp4ij` as plugin Id. | ||
|
||
You need [to declare dependency in your plugin.xml](https://plugins.jetbrains.com/docs/intellij/plugin-dependencies.html#3-dependency-declaration-in-pluginxml) like this: | ||
|
||
```xml | ||
<idea-plugin> | ||
... | ||
|
||
<depends>com.redhat.devtools.lsp4ij</depends> | ||
|
||
... | ||
</idea-plugin> | ||
``` | ||
|
||
### Exclude all LSP4J dependencies | ||
|
||
LSP4IJ depends on [Eclipse LSP4J](https://github.com/eclipse-lsp4j/lsp4j) (Java binding for the [Language Server Protocol](https://microsoft.github.io/language-server-protocol) and the [Debug Adapter Protocol](https://microsoft.github.io/debug-adapter-protocol).). It uses a given version of LSPJ and their classes are loaded in the LSP4IJ plugin class loader. | ||
|
||
Your IntelliJ Plugin should use `the same LSP4J classes than LSP4IJ` to avoid some `ClassCastException` errors. To do that you need to `exclude all LSP4J dependencies` from your plugin. | ||
|
||
Here a sample used in [Quarkus Tools](https://github.com/redhat-developer/intellij-quarkus) in [build.gradle.kts](https://github.com/redhat-developer/intellij-quarkus/blob/main/build.gradle.kts) to exclude LSP4J dependency from the [Qute Language Server](https://github.com/redhat-developer/quarkus-ls/tree/master/qute.ls) which have a dependency to LSP4J: | ||
|
||
``` | ||
implementation("com.redhat.microprofile:com.redhat.qute.ls:0.17.0) { | ||
exclude("org.eclipse.lsp4j") | ||
} | ||
``` | ||
|
||
## Declare server | ||
|
||
|
||
### StreamConnectionProvider Implementation | ||
|
||
You need to implement the [StreamConnectionProvider](https://github.com/redhat-developer/lsp4ij/blob/main/src/main/java/com/redhat/devtools/lsp4ij/server/StreamConnectionProvider.java) API which manages: | ||
|
||
* start of your language server | ||
* returns the input/error stream of LSP requests, responses, notifications. | ||
|
||
Generally, the language server is started with a process by using a runtime like Java, NodeJS, etc. In this case you need to extend [ProcessStreamConnectionProvider](https://github.com/redhat-developer/lsp4ij/blob/main/src/main/java/com/redhat/devtools/lsp4ij/server/ProcessStreamConnectionProvider.java) | ||
|
||
Here a basic sample which starts the `path/to/my/language/server/main.js` language server written in JavaScript with NodeJS runtime "path/to/nodejs/node.exe": | ||
|
||
```java | ||
package my.language.server; | ||
|
||
import com.redhat.devtools.lsp4ij.server.ProcessStreamConnectionProvider; | ||
|
||
import java.util.Arrays; | ||
import java.util.List; | ||
|
||
public class MyLanguageServer extends ProcessStreamConnectionProvider { | ||
|
||
public MyLanguageServer() { | ||
List<String> commands = Arrays.asList("path/to/nodejs/node.exe", "path/to/my/language/server/main.js"); | ||
super.setCommands(commands); | ||
} | ||
} | ||
``` | ||
|
||
If your language server is written in Java, to build the command, you can use [JavaProcessCommandBuilder](https://github.com/redhat-developer/lsp4ij/blob/main/src/main/java/com/redhat/devtools/lsp4ij/server/JavaProcessCommandBuilder.java): | ||
|
||
```java | ||
package my.language.server; | ||
|
||
import com.intellij.openapi.project.Project; | ||
import com.redhat.devtools.lsp4ij.server.JavaProcessCommandBuilder; | ||
import com.redhat.devtools.lsp4ij.server.ProcessStreamConnectionProvider; | ||
|
||
import java.util.Arrays; | ||
import java.util.List; | ||
|
||
public class MyLanguageServer extends ProcessStreamConnectionProvider { | ||
|
||
public MyLanguageServer(Project project) { | ||
List<String> commands = new JavaProcessCommandBuilder(project, "myLanguageServerId") | ||
.setJar("path/to/my/language/server/server.jar") | ||
.create(); | ||
super.setCommands(commands); | ||
} | ||
} | ||
``` | ||
|
||
This builder takes care of filling command with Java runtime and generate the command with debug if the settings of the language server `myLanguageServerId` defines a debug port. | ||
|
||
You can see a full sample with [QuteServer](https://github.com/redhat-developer/intellij-quarkus/blob/main/src/main/java/com/redhat/devtools/intellij/qute/lsp/QuteServer.java) | ||
|
||
### LanguageClientImpl | ||
|
||
It is not required but you can override the [LanguageClientImpl](https://github.com/redhat-developer/lsp4ij/blob/main/src/main/java/com/redhat/devtools/lsp4ij/client/LanguageClientImpl.java) to, for instance: | ||
|
||
* add some IJ listeners when language client is created. | ||
* override some LSP methods. | ||
|
||
```java | ||
package my.language.server; | ||
|
||
import com.intellij.openapi.project.Project; | ||
import com.redhat.devtools.lsp4ij.client.LanguageClientImpl; | ||
|
||
public class MyLanguageClient extends LanguageClientImpl { | ||
public MyLanguageClient(Project project) { | ||
super(project); | ||
} | ||
} | ||
``` | ||
|
||
If your language server manages custom LSP requests, it is advised to extend [IndexAwareLanguageClient](https://github.com/redhat-developer/lsp4ij/blob/main/src/main/java/com/redhat/devtools/lsp4ij/client/IndexAwareLanguageClient.java) | ||
|
||
You can see a full sample with [QuteLanguageClient](https://github.com/redhat-developer/intellij-quarkus/blob/main/src/main/java/com/redhat/devtools/intellij/qute/lsp/QuteLanguageClient.java) | ||
|
||
## Declare server with extension point | ||
|
||
The last step is to declare the server in your plugin.xml with `com.redhat.devtools.lsp4ij.server` extension point | ||
to use your `my.language.server.MyLanguageServer` and `my.language.server.MyLanguageClient`: | ||
|
||
```xml | ||
<idea-plugin> | ||
|
||
<extensions defaultExtensionNs="com.redhat.devtools.lsp4ij"> | ||
<server id="myLanguageServerId" | ||
label="My Language Server" | ||
class="my.language.server.MyLanguageServer" | ||
clientImpl="my.language.server.MyLanguageClient" | ||
scope="project"> | ||
<description><![CDATA[ | ||
Some description written in HTML to display it in LSP consoles and Language Servers settings. | ||
]]> | ||
</description> | ||
</server> | ||
</extensions> | ||
|
||
</idea-plugin> | ||
``` | ||
|
||
Once the declaration is done, your server should appear in the LSP console: | ||
|
||
![My LanguageServer in LSP Console](./images/MyLanguageServerInLSPConsole.png) | ||
|
||
## Declare language mapping with extension point | ||
|
||
Once the server is defined, you need to associate an IntelliJ language with the `server` defined by the id attribute | ||
with the `com.redhat.devtools.lsp4ij.languageMapping` extension point. | ||
|
||
Her is sample snippet to associate the `XML` language with the `myLanguageServerId` server: | ||
|
||
```xml | ||
</idea-plugin> | ||
|
||
<extensions defaultExtensionNs="com.redhat.devtools.lsp4ij"> | ||
|
||
<languageMapping language="XML" | ||
serverId="myLanguageServerId" /> | ||
|
||
</extensions> | ||
``` | ||
|
||
If the language check is not enough, you can implement a custom [DocumentMatcher](https://github.com/redhat-developer/lsp4ij/blob/main/src/main/java/com/redhat/devtools/lsp4ij/DocumentMatcher.java). | ||
For instance your language server could be mapped to the `Java` language, and you could implement a DocumentMatcher | ||
to check if the module containing the file contains certain Java classes in its classpath. | ||
|
||
The DocumentMatcher is executed in a non blocking read action. | ||
|
||
A document matcher looks like this: | ||
|
||
```java | ||
package my.language.server; | ||
|
||
import com.intellij.openapi.project.Project; | ||
import com.intellij.openapi.vfs.VirtualFile; | ||
import com.redhat.devtools.lsp4ij.AbstractDocumentMatcher; | ||
import org.jetbrains.annotations.NotNull; | ||
|
||
public class MyDocumentMatcher extends AbstractDocumentMatcher { | ||
|
||
@Override | ||
public boolean match(@NotNull VirtualFile virtualFile, @NotNull Project project) { | ||
return true; | ||
} | ||
} | ||
``` | ||
|
||
and it must be registered as language mapping, with the `documentMatcher` attribute: | ||
|
||
```xml | ||
</idea-plugin> | ||
|
||
<extensions defaultExtensionNs="com.redhat.devtools.lsp4ij"> | ||
|
||
<languageMapping language="XML" | ||
serverId="myLanguageServerId" | ||
documentMatcher="my.language.server.MyDocumentMatcher" /> | ||
|
||
</extensions> | ||
``` | ||
|
||
## Add specific IJ features | ||
|
||
When it is possible, LSP4IJ declare the IJ feature with `any` language. | ||
|
||
For instance `textDocument/publishDiagnostics` is managed with `externalAnnotator` in LSP4IJ | ||
with an empty language attribute, in order to support all languages: | ||
|
||
```xml | ||
<!-- LSP textDocument/publishDiagnostics notification support --> | ||
<externalAnnotator | ||
id="LSPDiagnosticAnnotator" | ||
language="" | ||
implementationClass="com.redhat.devtools.lsp4ij.operations.diagnostics.LSPDiagnosticAnnotator"/> | ||
``` | ||
|
||
Some LSP features (Hover, Inlay Hints, CodeLens) have to be mapped specifically to your language, in your plugin.xml: | ||
|
||
* `textDocument/hover`: | ||
|
||
```xml | ||
<lang.documentationProvider | ||
language="MyLanguage" | ||
implementationClass="com.redhat.devtools.lsp4ij.operations.documentation.LSPDocumentationProvider" | ||
order="first"/> | ||
``` | ||
|
||
* `textDocument/codeLens`: | ||
|
||
```xml | ||
<codeInsight.inlayProvider | ||
language="MyLanguage" | ||
implementationClass="com.redhat.devtools.lsp4ij.operations.codelens.LSPCodelensInlayProvider"/> | ||
``` | ||
|
||
* `textDocument/inlayHint`: | ||
|
||
```xml | ||
<codeInsight.inlayProvider | ||
language="MyLanguage" | ||
implementationClass="com.redhat.devtools.lsp4ij.operations.codelens.LSPInlayHintInlayProvider"/> | ||
``` |
Oops, something went wrong.