Skip to content

Commit

Permalink
feat: macro support in the start ls command
Browse files Browse the repository at this point in the history
Fixes 336

Signed-off-by: azerr <[email protected]>
  • Loading branch information
angelozerr authored and fbricon committed Jun 11, 2024
1 parent 33a32b4 commit fc8f1a3
Show file tree
Hide file tree
Showing 7 changed files with 113 additions and 8 deletions.
4 changes: 4 additions & 0 deletions docs/DeveloperGuide.md
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,8 @@ Here is a sample snippet to associate the `XML` language with the `myLanguageSer
</extensions>
```
#### Language ID
Some language servers use
the [TextDocumentItem#languageId](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocumentItem)
field to identify the document on the server side.
Expand All @@ -267,6 +269,8 @@ To do that, you can declare it with the `languageId` attribute:
</extensions>
```
#### Document Matcher
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
Expand Down
31 changes: 27 additions & 4 deletions docs/UserDefinedLanguageServer.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@ The main idea is to:

* install the language server and its requirements(ex : `Node.js` to
execute a language server written in JavaScript/TypeScript),
* declare the command which starts the language server.
* declare the command that starts the language server.
* associate the language server with the proper files (identified by IntelliJ Language, File Type or file name pattern)

## New Language Server dialog

To create a new `User-defined language server` you need to open the `New Language Server` dialog, either:
In order to create a new `User-defined language server`, you need to open the `New Language Server` dialog, either:

* from the menu on the right of the LSP console:

Expand All @@ -30,7 +30,7 @@ Once you clicked on either of them, the dialog will appear:

### Server tab

The `Server tab` requires to fill the `server name` and the `command` which will start the language server.
The `Server tab` requires the `server name` and `command` fields to be set.

Here is a sample with the [typescript-language-server](https://github.com/typescript-language-server/typescript-language-server):

Expand All @@ -42,7 +42,7 @@ The environment variables accessible by the process are populated with
[EnvironmentUtil.getEnvironmentMap()](https://github.com/JetBrains/intellij-community/blob/3a527a2c9b56209c09852ba7bc89d80bc31e1c04/platform/util/src/com/intellij/util/EnvironmentUtil.java#L85)
which retrieves system variables.

It is possible too to add custom environment variables via the field `Environment variables`:
It is also possible to add custom environment variables via the `Environment variables` field:

![Environment Variables](./images/user-defined-ls/EnvironmentVariables.png)

Expand All @@ -51,6 +51,21 @@ Depending on your OS, the environment variables may not be accessible. To make s
* with `Windows OS`: `cmd /c command_to_start_your_ls`
* with `Linux`, `Mac OS`: `sh -c command_to_start_your_ls`

#### Macro syntax

You can use [built-in macros](https://www.jetbrains.com/help/idea/built-in-macros.html) in your command. You could, for instance, store the language server in your project (to share it with your team)
and write a command that references it in a portable way to start it.

That command might look like this:

`$PROJECT_DIR$/path/to/your/start/command`

Here is an example with Scala Language Server's `metals.bat` stored at the root of the project:

![Macro syntax](./images/user-defined-ls/Macro.png)

When commands contain macros, their resolved value is visible below the `Command` field.

### Mappings tab

The `Mappings tab` provides the capability to `associate the language server with the proper files` identified by:
Expand All @@ -73,6 +88,14 @@ NOTE: it is better to use file name pattern instead of creating custom file type
IntelliJ Community support `TypeScript syntax coloration` with `TextMate`. If you define a file type, you will
lose syntax coloration.

#### Language ID

When you declare mapping, you can fill the `Language ID` column which is used to declare the LSP [TextDocumentItem#languageId](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocumentItem)
to identify the document on the server side.

For instance the [vscode-css-languageservice](https://github.com/microsoft/vscode-css-languageservice) (used by the vscode CSS language server) expects the `languageId` to be `css` or `less`.
To do that, you can declare it with the `languageId` attribute:

### Configuration tab

The `Configuration tab` allows to configure the language server with the expected (generally JSON format) configuration.
Expand Down
Binary file added docs/images/user-defined-ls/Macro.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ public NewLanguageServerDialog(@NotNull Project project) {
// Template combo
createTemplateCombo(builder);
// Create server name, command line, mappings, configuration UI
this.languageServerPanel = new LanguageServerPanel(builder, null, LanguageServerPanel.EditionMode.NEW_USER_DEFINED);
this.languageServerPanel = new LanguageServerPanel(builder, null, LanguageServerPanel.EditionMode.NEW_USER_DEFINED, project);

// Add validation
addValidator(this.languageServerPanel.getServerName());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

import com.google.gson.JsonParser;
import com.google.gson.annotations.SerializedName;
import com.intellij.execution.util.ProgramParametersUtil;
import com.intellij.openapi.project.Project;
import com.redhat.devtools.lsp4ij.client.LanguageClientImpl;
import com.redhat.devtools.lsp4ij.server.StreamConnectionProvider;
Expand Down Expand Up @@ -64,7 +65,22 @@ public UserDefinedLanguageServerDefinition(@NotNull String id,

@Override
public @NotNull StreamConnectionProvider createConnectionProvider(@NotNull Project project) {
return new UserDefinedStreamConnectionProvider(commandLine, userEnvironmentVariables, includeSystemEnvironmentVariables, this);
String resolvedCommandLine = resolveCommandLine(commandLine, project);
return new UserDefinedStreamConnectionProvider(resolvedCommandLine,
userEnvironmentVariables,
includeSystemEnvironmentVariables,
this);
}

/**
* Returns the resolved command line with expanded macros.
*
* @param project the project.
* @return the resolved command line with expanded macros.
* @see <a href="https://www.jetbrains.com/help/idea/built-in-macros.html">Built In Macro</a>
*/
public static String resolveCommandLine(@NotNull String commandLine, @NotNull Project project) {
return ProgramParametersUtil.expandPathAndMacros(commandLine, null, project);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,7 @@ private JPanel createSettings(JComponent description, boolean launchingServerDef
this.languageServerPanel = new LanguageServerPanel(builder,
description,
launchingServerDefinition ? LanguageServerPanel.EditionMode.EDIT_USER_DEFINED :
LanguageServerPanel.EditionMode.EDIT_EXTENSION);
LanguageServerPanel.EditionMode.EDIT_EXTENSION, project);
this.mappingPanel = languageServerPanel.getMappingsPanel();
return builder
.addComponentFillVertically(new JPanel(), 50)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@

import com.intellij.execution.configuration.EnvironmentVariablesComponent;
import com.intellij.ide.BrowserUtil;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.ComboBox;
import com.intellij.openapi.util.NlsContexts;
import com.intellij.ui.ContextHelpLabel;
import com.intellij.ui.PortField;
import com.intellij.ui.SimpleListCellRenderer;
Expand All @@ -22,13 +24,17 @@
import com.intellij.ui.components.JBTextField;
import com.intellij.ui.scale.JBUIScale;
import com.intellij.util.ui.FormBuilder;
import com.intellij.util.ui.UIUtil;
import com.intellij.util.ui.components.BorderLayoutPanel;
import com.redhat.devtools.lsp4ij.LanguageServerBundle;
import com.redhat.devtools.lsp4ij.server.definition.launching.UserDefinedLanguageServerDefinition;
import com.redhat.devtools.lsp4ij.settings.ErrorReportingKind;
import com.redhat.devtools.lsp4ij.settings.ServerTrace;
import org.jetbrains.annotations.NotNull;

import javax.swing.*;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import java.awt.*;

/**
Expand All @@ -43,6 +49,10 @@
*/
public class LanguageServerPanel {

private static final int COMMAND_LENGTH_MAX = 140;

private final Project project;

public enum EditionMode {
NEW_USER_DEFINED,
EDIT_USER_DEFINED,
Expand All @@ -62,7 +72,8 @@ public enum EditionMode {

private LanguageServerInitializationOptionsWidget initializationOptionsWidget;

public LanguageServerPanel(FormBuilder builder, JComponent description, EditionMode mode) {
public LanguageServerPanel(FormBuilder builder, JComponent description, EditionMode mode, Project project) {
this.project = project;
createUI(builder, description, mode);
}

Expand Down Expand Up @@ -184,7 +195,58 @@ private void createCommandLineField(FormBuilder builder) {
commandLine = new CommandLineWidget();
JBScrollPane scrollPane = new JBScrollPane(commandLine);
scrollPane.setMinimumSize(new Dimension(JBUIScale.scale(600), JBUIScale.scale(100)));
JLabel previewCommandLabel = createLabelForComponent("", scrollPane);
updatePreviewCommand(commandLine, previewCommandLabel, project);
builder.addLabeledComponent(LanguageServerBundle.message("language.server.command"), scrollPane, true);
builder.addComponent(previewCommandLabel, 0);
}

/**
* Update preview command label with expanded macro result if needed.
*
* @param commandLine the command line which could contains macro syntax.
* @param previewCommandLabel the preview command label.
* @param project the project.
* @see <a href="https://www.jetbrains.com/help/idea/built-in-macros.html">Built In Macro</a>
*/
private static void updatePreviewCommand(@NotNull CommandLineWidget commandLine,
@NotNull JLabel previewCommandLabel,
@NotNull Project project) {
commandLine.getDocument().addDocumentListener(new DocumentListener() {
@Override
public void insertUpdate(DocumentEvent e) {
updateLabel(previewCommandLabel);
}

private void updateLabel(JLabel previewCommandLabel) {
String preview = UserDefinedLanguageServerDefinition.resolveCommandLine(commandLine.getText(), project);
if (preview.equals(commandLine.getText())) {
previewCommandLabel.setToolTipText("");
previewCommandLabel.setText("");
} else {
previewCommandLabel.setToolTipText(preview);
String shortPreview = preview.length() > COMMAND_LENGTH_MAX ? preview.substring(0, COMMAND_LENGTH_MAX) + "..." : preview;
previewCommandLabel.setText(shortPreview);
}
}

@Override
public void removeUpdate(DocumentEvent e) {
updateLabel(previewCommandLabel);
}

@Override
public void changedUpdate(DocumentEvent e) {
updateLabel(previewCommandLabel);
}
});
}


private static JLabel createLabelForComponent(@NotNull @NlsContexts.Label String labelText, @NotNull JComponent component) {
JLabel label = new JLabel(UIUtil.replaceMnemonicAmpersand(labelText));
label.setLabelFor(component);
return label;
}

private void createConfigurationField(FormBuilder builder) {
Expand Down

0 comments on commit fc8f1a3

Please sign in to comment.