Skip to content

Commit a1287a9

Browse files
committed
Add LSP support [paid versions of IDEA only] (#68)
1 parent 736f766 commit a1287a9

10 files changed

+467
-6
lines changed

CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
### Added
66

77
- Plugin logo for easier recognition
8+
- Experimental Language Server support using IDEA's LSP API (#68)
9+
[(Only works for paid versions of IDEA 😞)](https://blog.jetbrains.com/platform/2023/07/lsp-for-plugin-developers/#supported-ides)
810

911
### Changed
1012

@@ -17,6 +19,8 @@
1719

1820
### Removed
1921

22+
- Support for IDEA 2023.1
23+
2024
### Fixed
2125

2226
- Variables behind `inherit` keyword not correctly resolved during highlighting

build.gradle.kts

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import org.jetbrains.changelog.Changelog
22
import org.jetbrains.changelog.markdownToHTML
3-
import org.jetbrains.intellij.tasks.RunPluginVerifierTask
3+
import org.jetbrains.intellij.tasks.RunPluginVerifierTask.FailureLevel
4+
import java.util.EnumSet
45

56
plugins {
67
id("java")
@@ -167,7 +168,7 @@ tasks {
167168
}
168169

169170
runPluginVerifier {
170-
failureLevel = RunPluginVerifierTask.FailureLevel.ALL
171+
failureLevel = EnumSet.complementOf(EnumSet.of(FailureLevel.EXPERIMENTAL_API_USAGES))
171172
// Version 1.364 seems to be broken and always complains about supposedly missing 'plugin.xml':
172173
// https://youtrack.jetbrains.com/issue/MP-6388
173174
verifierVersion = "1.307"

gradle.properties

+3-3
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@
44
pluginGroup = org.nixos.idea
55
pluginName = NixIDEA
66
pluginVersion = 0.4.0.13
7-
pluginSinceBuild = 231
7+
pluginSinceBuild = 232
88
pluginUntilBuild = 241.*
99

10-
platformType = IC
11-
platformVersion = 2023.1.6
10+
platformType = IU
11+
platformVersion = 2023.2.6
1212

1313
# Gradle Configuration
1414
# -> https://docs.gradle.org/current/userguide/build_environment.html#sec:gradle_configuration_properties
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package org.nixos.idea.lsp;
2+
3+
import com.intellij.execution.ExecutionException;
4+
import com.intellij.execution.configurations.GeneralCommandLine;
5+
import com.intellij.openapi.project.Project;
6+
import com.intellij.openapi.vfs.VirtualFile;
7+
import com.intellij.platform.lsp.api.ProjectWideLspServerDescriptor;
8+
import com.intellij.util.execution.ParametersListUtil;
9+
import org.jetbrains.annotations.NotNull;
10+
import org.nixos.idea.file.NixFileType;
11+
12+
import java.util.List;
13+
14+
@SuppressWarnings("UnstableApiUsage")
15+
final class NixLspServerDescriptor extends ProjectWideLspServerDescriptor {
16+
17+
private final String myCommand;
18+
19+
NixLspServerDescriptor(@NotNull Project project, NixLspSettings settings) {
20+
super(project, "Nix");
21+
myCommand = settings.getCommand();
22+
}
23+
24+
@Override
25+
public @NotNull GeneralCommandLine createCommandLine() throws ExecutionException {
26+
List<String> argv = ParametersListUtil.parse(myCommand, false, true);
27+
return new GeneralCommandLine(argv);
28+
}
29+
30+
@Override
31+
public boolean isSupportedFile(@NotNull VirtualFile virtualFile) {
32+
return virtualFile.getFileType() == NixFileType.INSTANCE;
33+
}
34+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package org.nixos.idea.lsp;
2+
3+
import com.intellij.openapi.project.Project;
4+
import com.intellij.openapi.vfs.VirtualFile;
5+
import com.intellij.platform.lsp.api.LspServerSupportProvider;
6+
import org.jetbrains.annotations.NotNull;
7+
import org.nixos.idea.file.NixFileType;
8+
9+
@SuppressWarnings("UnstableApiUsage")
10+
public final class NixLspServerSupportProvider implements LspServerSupportProvider {
11+
@Override
12+
public void fileOpened(@NotNull Project project, @NotNull VirtualFile virtualFile, @NotNull LspServerStarter lspServerStarter) {
13+
if (virtualFile.getFileType() == NixFileType.INSTANCE) {
14+
NixLspSettings settings = NixLspSettings.getInstance();
15+
if (settings.isEnabled()) {
16+
lspServerStarter.ensureServerStarted(new NixLspServerDescriptor(project, settings));
17+
}
18+
}
19+
}
20+
21+
// TODO: Uncomment with IDEA 2024.1
22+
//@Override
23+
//public @NotNull LspServerWidgetItem createLspServerWidgetItem(@NotNull LspServer lspServer, @Nullable VirtualFile currentFile) {
24+
// return new LspServerWidgetItem(lspServer, currentFile, NixIcons.FILE, NixLspSettingsConfigurable.class);
25+
//}
26+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package org.nixos.idea.lsp;
2+
3+
import com.intellij.openapi.application.ApplicationManager;
4+
import com.intellij.openapi.components.PersistentStateComponent;
5+
import com.intellij.openapi.components.RoamingType;
6+
import com.intellij.openapi.components.State;
7+
import com.intellij.openapi.components.Storage;
8+
import org.jetbrains.annotations.NotNull;
9+
10+
import java.util.ArrayDeque;
11+
import java.util.Collection;
12+
import java.util.Collections;
13+
import java.util.Deque;
14+
15+
@State(name = "NixLspSettings", storages = @Storage(value = "nix-idea-tools.xml", roamingType = RoamingType.DISABLED))
16+
public final class NixLspSettings implements PersistentStateComponent<NixLspSettings.State> {
17+
18+
// TODO: Use RoamingType.LOCAL with 2024.1
19+
20+
// Documentation:
21+
// https://plugins.jetbrains.com/docs/intellij/persisting-state-of-components.html
22+
23+
private static final int MAX_HISTORY_SIZE = 5;
24+
25+
private @NotNull State myState = new State();
26+
27+
public static @NotNull NixLspSettings getInstance() {
28+
return ApplicationManager.getApplication().getService(NixLspSettings.class);
29+
}
30+
31+
public boolean isEnabled() {
32+
return myState.enabled;
33+
}
34+
35+
public void setEnabled(boolean enabled) {
36+
myState.enabled = enabled;
37+
}
38+
39+
public @NotNull String getCommand() {
40+
return myState.command;
41+
}
42+
43+
public void setCommand(@NotNull String command) {
44+
myState.command = command;
45+
addToHistory(command);
46+
}
47+
48+
public @NotNull Collection<String> getCommandHistory() {
49+
return Collections.unmodifiableCollection(myState.history);
50+
}
51+
52+
private void addToHistory(@NotNull String command) {
53+
Deque<String> history = myState.history;
54+
history.remove(command);
55+
history.addFirst(command);
56+
while (history.size() > MAX_HISTORY_SIZE) {
57+
history.removeLast();
58+
}
59+
}
60+
61+
@SuppressWarnings("ClassEscapesDefinedScope")
62+
@Override
63+
public void loadState(@NotNull State state) {
64+
myState = state;
65+
}
66+
67+
@SuppressWarnings("ClassEscapesDefinedScope")
68+
@Override
69+
public @NotNull State getState() {
70+
return myState;
71+
}
72+
73+
static final class State {
74+
public boolean enabled = false;
75+
public @NotNull String command = "";
76+
public Deque<String> history = new ArrayDeque<>();
77+
}
78+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
package org.nixos.idea.lsp;
2+
3+
import com.intellij.openapi.options.Configurable;
4+
import com.intellij.openapi.options.ConfigurationException;
5+
import com.intellij.openapi.options.SearchableConfigurable;
6+
import com.intellij.openapi.project.Project;
7+
import com.intellij.openapi.project.ProjectManager;
8+
import com.intellij.openapi.util.NlsContexts;
9+
import com.intellij.platform.lsp.api.LspServerManager;
10+
import com.intellij.ui.RawCommandLineEditor;
11+
import com.intellij.ui.TitledSeparator;
12+
import com.intellij.ui.components.JBCheckBox;
13+
import com.intellij.util.ui.FormBuilder;
14+
import org.jetbrains.annotations.NonNls;
15+
import org.jetbrains.annotations.NotNull;
16+
import org.jetbrains.annotations.Nullable;
17+
import org.nixos.idea.lsp.ui.CommandSuggestionsPopup;
18+
19+
import javax.swing.JComponent;
20+
import javax.swing.JPanel;
21+
22+
public class NixLspSettingsConfigurable implements SearchableConfigurable, Configurable.Beta {
23+
24+
private @Nullable JBCheckBox myEnabled;
25+
private @Nullable RawCommandLineEditor myCommand;
26+
27+
@Override
28+
public @NotNull @NonNls String getId() {
29+
return "org.nixos.idea.lsp.NixLspSettings";
30+
}
31+
32+
@Override
33+
public @NlsContexts.ConfigurableName String getDisplayName() {
34+
return "Language Server (LSP)";
35+
}
36+
37+
@Override
38+
public @Nullable JComponent createComponent() {
39+
myEnabled = new JBCheckBox("Enable language server");
40+
myEnabled.addChangeListener(e -> updateUiState());
41+
42+
myCommand = new RawCommandLineEditor();
43+
myCommand.getEditorField().getEmptyText().setText("Command to start Language Server");
44+
myCommand.getEditorField().getAccessibleContext().setAccessibleName("Command to start Language Server");
45+
myCommand.getEditorField().setMargin(myEnabled.getMargin());
46+
new CommandSuggestionsPopup(myCommand, NixLspSettings.getInstance().getCommandHistory()).install();
47+
48+
return FormBuilder.createFormBuilder()
49+
.addComponent(myEnabled)
50+
.addComponent(new TitledSeparator("Language Server Configuration"))
51+
.addLabeledComponent("Command: ", myCommand)
52+
.addComponentFillVertically(new JPanel(), 0)
53+
.getPanel();
54+
}
55+
56+
@Override
57+
public void reset() {
58+
assert myEnabled != null;
59+
assert myCommand != null;
60+
61+
NixLspSettings settings = NixLspSettings.getInstance();
62+
myEnabled.setSelected(settings.isEnabled());
63+
myCommand.setText(settings.getCommand());
64+
65+
updateUiState();
66+
}
67+
68+
@SuppressWarnings("UnstableApiUsage")
69+
@Override
70+
public void apply() throws ConfigurationException {
71+
assert myEnabled != null;
72+
assert myCommand != null;
73+
74+
NixLspSettings settings = NixLspSettings.getInstance();
75+
settings.setEnabled(myEnabled.isSelected());
76+
settings.setCommand(myCommand.getText());
77+
78+
for (Project project : ProjectManager.getInstance().getOpenProjects()) {
79+
LspServerManager.getInstance(project).stopAndRestartIfNeeded(NixLspServerSupportProvider.class);
80+
}
81+
}
82+
83+
@Override
84+
public boolean isModified() {
85+
assert myEnabled != null;
86+
assert myCommand != null;
87+
88+
NixLspSettings settings = NixLspSettings.getInstance();
89+
return Configurable.isCheckboxModified(myEnabled, settings.isEnabled()) ||
90+
Configurable.isFieldModified(myCommand.getTextField(), settings.getCommand());
91+
}
92+
93+
private void updateUiState() {
94+
assert myEnabled != null;
95+
assert myCommand != null;
96+
97+
myCommand.setEnabled(myEnabled.isSelected());
98+
}
99+
}

0 commit comments

Comments
 (0)