Skip to content

Commit 0a5b4e4

Browse files
cottandJojOatXGME
andauthored
Formatting based on external formatter (#80)
Co-authored-by: Johannes Spangenberg <[email protected]>
1 parent b25d38d commit 0a5b4e4

11 files changed

+363
-24
lines changed

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,5 @@ hs_err_pid*
2323
*.iml
2424
**/.gradle
2525
build
26+
27+
src/gen

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
### Added
66

7+
- Support for code formatting via external commands ([#80](https://github.com/NixOS/nix-idea/pull/80))
8+
79
### Changed
810

911
### Deprecated
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
package org.nixos.idea.format;
2+
3+
import com.intellij.execution.ExecutionException;
4+
import com.intellij.execution.configurations.GeneralCommandLine;
5+
import com.intellij.execution.process.CapturingProcessAdapter;
6+
import com.intellij.execution.process.OSProcessHandler;
7+
import com.intellij.execution.process.ProcessEvent;
8+
import com.intellij.formatting.service.AsyncDocumentFormattingService;
9+
import com.intellij.formatting.service.AsyncFormattingRequest;
10+
import com.intellij.openapi.util.NlsSafe;
11+
import com.intellij.psi.PsiFile;
12+
import com.intellij.util.execution.ParametersListUtil;
13+
import org.jetbrains.annotations.NonNls;
14+
import org.jetbrains.annotations.NotNull;
15+
import org.jetbrains.annotations.Nullable;
16+
import org.nixos.idea.file.NixFile;
17+
import org.nixos.idea.lang.NixLanguage;
18+
import org.nixos.idea.settings.NixExternalFormatterSettings;
19+
20+
import java.io.IOException;
21+
import java.io.OutputStream;
22+
import java.nio.charset.StandardCharsets;
23+
import java.nio.file.Files;
24+
import java.util.EnumSet;
25+
import java.util.List;
26+
import java.util.Set;
27+
28+
public final class NixExternalFormatter extends AsyncDocumentFormattingService {
29+
30+
@Override
31+
protected @NotNull String getNotificationGroupId() {
32+
return NixLanguage.NOTIFICATION_GROUP_ID;
33+
}
34+
35+
@Override
36+
protected @NotNull @NlsSafe String getName() {
37+
return "NixIDEA";
38+
}
39+
40+
@Override
41+
public @NotNull Set<Feature> getFeatures() {
42+
return EnumSet.noneOf(Feature.class);
43+
}
44+
45+
@Override
46+
public boolean canFormat(@NotNull PsiFile psiFile) {
47+
return psiFile instanceof NixFile;
48+
}
49+
50+
51+
@Override
52+
protected @Nullable FormattingTask createFormattingTask(@NotNull AsyncFormattingRequest request) {
53+
NixExternalFormatterSettings nixSettings = NixExternalFormatterSettings.getInstance();
54+
if (!nixSettings.isFormatEnabled()) {
55+
return null;
56+
}
57+
58+
var ioFile = request.getIOFile();
59+
if (ioFile == null) return null;
60+
61+
@NonNls
62+
var command = nixSettings.getFormatCommand();
63+
List<String> argv = ParametersListUtil.parse(command, false, true);
64+
65+
var commandLine = new GeneralCommandLine(argv);
66+
67+
try {
68+
var handler = new OSProcessHandler(commandLine.withCharset(StandardCharsets.UTF_8));
69+
OutputStream processInput = handler.getProcessInput();
70+
return new FormattingTask() {
71+
@Override
72+
public void run() {
73+
handler.addProcessListener(new CapturingProcessAdapter() {
74+
75+
@Override
76+
public void processTerminated(@NotNull ProcessEvent event) {
77+
int exitCode = event.getExitCode();
78+
if (exitCode == 0) {
79+
request.onTextReady(getOutput().getStdout());
80+
} else {
81+
request.onError("NixIDEA", getOutput().getStderr());
82+
}
83+
}
84+
});
85+
handler.startNotify();
86+
try {
87+
Files.copy(ioFile.toPath(), processInput);
88+
processInput.flush();
89+
processInput.close();
90+
} catch (IOException e) {
91+
handler.destroyProcess();
92+
request.onError("NixIDEA", e.getMessage());
93+
}
94+
}
95+
96+
@Override
97+
public boolean cancel() {
98+
handler.destroyProcess();
99+
return true;
100+
}
101+
102+
@Override
103+
public boolean isRunUnderProgress() {
104+
return true;
105+
}
106+
};
107+
} catch (ExecutionException e) {
108+
request.onError("NixIDEA", e.getMessage());
109+
return null;
110+
}
111+
}
112+
}

src/main/java/org/nixos/idea/lang/NixLanguage.java

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
public class NixLanguage extends Language {
66
public static final NixLanguage INSTANCE = new NixLanguage();
7+
public static final String NOTIFICATION_GROUP_ID = "NixIDEA";
78

89
private NixLanguage() {
910
super("Nix");

src/main/java/org/nixos/idea/lsp/NixLspSettings.java

+2-3
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,16 @@
66
import com.intellij.openapi.components.State;
77
import com.intellij.openapi.components.Storage;
88
import org.jetbrains.annotations.NotNull;
9+
import org.nixos.idea.settings.NixStoragePaths;
910

1011
import java.util.ArrayDeque;
1112
import java.util.Collection;
1213
import java.util.Collections;
1314
import java.util.Deque;
1415

15-
@State(name = "NixLspSettings", storages = @Storage(value = "nix-idea-tools.xml", roamingType = RoamingType.DISABLED))
16+
@State(name = "NixLspSettings", storages = @Storage(value = NixStoragePaths.TOOLS, roamingType = RoamingType.DISABLED))
1617
public final class NixLspSettings implements PersistentStateComponent<NixLspSettings.State> {
1718

18-
// TODO: Use RoamingType.LOCAL with 2024.1
19-
2019
// Documentation:
2120
// https://plugins.jetbrains.com/docs/intellij/persisting-state-of-components.html
2221

src/main/java/org/nixos/idea/lsp/NixLspSettingsConfigurable.java

+8-1
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,15 @@
1818

1919
import javax.swing.JComponent;
2020
import javax.swing.JPanel;
21+
import java.util.List;
2122

2223
public class NixLspSettingsConfigurable implements SearchableConfigurable, Configurable.Beta {
24+
private static final List<CommandSuggestionsPopup.Suggestion> BUILTIN_SUGGESTIONS = List.of(
25+
CommandSuggestionsPopup.Suggestion.builtin("<html>Use <b>nil</b> from nixpkgs</html>",
26+
"nix --extra-experimental-features \"nix-command flakes\" run nixpkgs#nil"),
27+
CommandSuggestionsPopup.Suggestion.builtin("<html>Use <b>nixd</b> from nixpkgs</html>",
28+
"nix --extra-experimental-features \"nix-command flakes\" run nixpkgs#nixd")
29+
);
2330

2431
private @Nullable JBCheckBox myEnabled;
2532
private @Nullable RawCommandLineEditor myCommand;
@@ -43,7 +50,7 @@ public class NixLspSettingsConfigurable implements SearchableConfigurable, Confi
4350
myCommand.getEditorField().getEmptyText().setText("Command to start Language Server");
4451
myCommand.getEditorField().getAccessibleContext().setAccessibleName("Command to start Language Server");
4552
myCommand.getEditorField().setMargin(myEnabled.getMargin());
46-
new CommandSuggestionsPopup(myCommand, NixLspSettings.getInstance().getCommandHistory()).install();
53+
new CommandSuggestionsPopup(myCommand, NixLspSettings.getInstance().getCommandHistory(), BUILTIN_SUGGESTIONS).install();
4754

4855
return FormBuilder.createFormBuilder()
4956
.addComponent(myEnabled)

src/main/java/org/nixos/idea/lsp/ui/CommandSuggestionsPopup.java

+12-12
Original file line numberDiff line numberDiff line change
@@ -37,20 +37,19 @@
3737
import java.util.stream.Stream;
3838

3939
public final class CommandSuggestionsPopup {
40-
// Implementation partially inspired by TextCompletionField
4140

42-
private static final List<Suggestion> BUILDIN_SUGGESTIONS = List.of(
43-
Suggestion.builtin("<html>Use <b>nil</b> from nixpkgs</html>",
44-
"nix --extra-experimental-features \"nix-command flakes\" run nixpkgs#nil"),
45-
Suggestion.builtin("<html>Use <b>nixd</b> from nixpkgs</html>",
46-
"nix --extra-experimental-features \"nix-command flakes\" run nixpkgs#nixd")
47-
);
41+
// Implementation partially inspired by TextCompletionField
4842

4943
private final @NotNull ExpandableTextField myEditor;
5044
private final @NotNull Collection<String> myHistory;
5145
private @Nullable ListPopup myPopup;
46+
private final @NotNull List<Suggestion> mySuggestions;
5247

53-
public CommandSuggestionsPopup(@NotNull RawCommandLineEditor commandLineEditor, @NotNull Collection<String> history) {
48+
public CommandSuggestionsPopup(@NotNull RawCommandLineEditor commandLineEditor,
49+
@NotNull Collection<String> history,
50+
@NotNull List<Suggestion> suggestions
51+
) {
52+
mySuggestions = suggestions;
5453
myEditor = commandLineEditor.getEditorField();
5554
myHistory = history;
5655
}
@@ -127,7 +126,8 @@ protected void process(KeyEvent aEvent) {
127126
switch (aEvent.getKeyCode()) {
128127
// Do no handle left and right key,
129128
// as it would prevent their usage in the text field while the popup is open.
130-
case KeyEvent.VK_LEFT, KeyEvent.VK_RIGHT -> {}
129+
case KeyEvent.VK_LEFT, KeyEvent.VK_RIGHT -> {
130+
}
131131
default -> super.process(aEvent);
132132
}
133133
}
@@ -138,13 +138,13 @@ public void onClosed(@NotNull LightweightWindowEvent event) {
138138
}
139139
}
140140

141-
private record Suggestion(
141+
public record Suggestion(
142142
@NotNull Icon icon,
143143
@NotNull String primaryText,
144144
@Nullable String secondaryText,
145145
@NotNull String command
146146
) {
147-
static @NotNull Suggestion builtin(@NotNull String name, @NotNull String command) {
147+
public static @NotNull Suggestion builtin(@NotNull String name, @NotNull String command) {
148148
return new Suggestion(AllIcons.Actions.Lightning, name, command, command);
149149
}
150150

@@ -163,7 +163,7 @@ private final class MyListPopupStep extends BaseListPopupStep<Suggestion> implem
163163

164164
public MyListPopupStep() {
165165
super(null, Stream.concat(
166-
BUILDIN_SUGGESTIONS.stream(),
166+
mySuggestions.stream(),
167167
myHistory.stream().map(Suggestion::history)
168168
).toList());
169169
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package org.nixos.idea.settings;
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 = "NixExternalFormatterSettings", storages = @Storage(value = NixStoragePaths.TOOLS, roamingType = RoamingType.DISABLED))
16+
public final class NixExternalFormatterSettings implements PersistentStateComponent<NixExternalFormatterSettings.State> {
17+
18+
// Documentation:
19+
// https://plugins.jetbrains.com/docs/intellij/persisting-state-of-components.html
20+
21+
private static final int MAX_HISTORY_SIZE = 5;
22+
23+
private @NotNull State myState = new State();
24+
25+
public static @NotNull NixExternalFormatterSettings getInstance() {
26+
return ApplicationManager.getApplication().getService(NixExternalFormatterSettings.class);
27+
}
28+
29+
public boolean isFormatEnabled() {
30+
return myState.enabled;
31+
}
32+
33+
public void setFormatEnabled(boolean enabled) {
34+
myState.enabled = enabled;
35+
}
36+
37+
public @NotNull String getFormatCommand() {
38+
return myState.command;
39+
}
40+
41+
public void setFormatCommand(@NotNull String command) {
42+
myState.command = command;
43+
addFormatCommandToHistory(command);
44+
}
45+
46+
public @NotNull Collection<String> getCommandHistory() {
47+
return Collections.unmodifiableCollection(myState.history);
48+
}
49+
50+
private void addFormatCommandToHistory(@NotNull String command) {
51+
Deque<String> history = myState.history;
52+
history.remove(command);
53+
history.addFirst(command);
54+
while (history.size() > MAX_HISTORY_SIZE) {
55+
history.removeLast();
56+
}
57+
}
58+
59+
@SuppressWarnings("ClassEscapesDefinedScope")
60+
@Override
61+
public void loadState(@NotNull State state) {
62+
myState = state;
63+
}
64+
65+
@SuppressWarnings("ClassEscapesDefinedScope")
66+
@Override
67+
public @NotNull State getState() {
68+
return myState;
69+
}
70+
71+
static final class State {
72+
public boolean enabled = false;
73+
public @NotNull String command = "";
74+
public Deque<String> history = new ArrayDeque<>();
75+
}
76+
}

0 commit comments

Comments
 (0)