Skip to content

Commit

Permalink
Add Elm 0.19 support
Browse files Browse the repository at this point in the history
  • Loading branch information
jlagerweij committed Aug 31, 2018
1 parent abc1fc1 commit 07c4150
Show file tree
Hide file tree
Showing 11 changed files with 146 additions and 70 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ Requires the [Elm](https://plugins.jetbrains.com/plugin/10268-elm) plugin to be

# External tools:

* External annotator using `elm-make`
* External annotator using `elm make`

Performs `elm-make` on your Elm files and shows compile error messages inline.
Performs `elm make` on your Elm files and shows compile error messages inline.
![Screenshot Inline Error Message](docs/images/screenshot-error-message.png)

Configure external tool in Intellij Preferences
Expand Down
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,4 @@ repositories {
}

group 'org.elm.tools.external'
version '1.3.0'
version '2.0.0'
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import com.intellij.lang.annotation.AnnotationHolder;
import com.intellij.lang.annotation.ExternalAnnotator;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.util.TextRange;
Expand All @@ -15,6 +16,7 @@
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiFile;

import org.elm.tools.external.ElmExternalToolsComponent;
import org.elm.tools.external.elmmake.ElmMake;
import org.elm.tools.external.elmmake.Problems;
import org.elm.tools.external.elmmake.Region;
Expand All @@ -28,6 +30,7 @@
import java.util.stream.Collectors;

public class ElmMakeExternalAnnotator extends ExternalAnnotator<AnnotatorFile, List<Problems>> {
private static final Logger LOG = Logger.getInstance(ElmExternalToolsComponent.class);
private static final String TAB = " ";

@Nullable
Expand Down Expand Up @@ -76,17 +79,20 @@ public List<Problems> doAnnotate(final AnnotatorFile annotatorFile) {

List<Problems> problems = ElmMake.execute(basePath.get(), elmPluginSettings.getNodeExecutable(), elmPluginSettings.getElmMakeExecutable(), canonicalPath);

return problems
final List<Problems> problemsForThisFile = problems
.stream()
.filter(res -> isIssueForCurrentFile(basePath.get(), canonicalPath, res))
.collect(Collectors.toList());

LOG.debug(problemsForThisFile.size() + " problems for file " + canonicalPath);
return problemsForThisFile;
}

private Optional<String> findElmPackageDirectory(PsiFile file) {
final PsiDirectory[] parent = new PsiDirectory[1];
ApplicationManager.getApplication().runReadAction(() -> {
parent[0] = file.getParent();
while (parent[0] != null && parent[0].isValid() && parent[0].findFile("elm-package.json") == null) {
while (parent[0] != null && parent[0].isValid() && parent[0].findFile("elm-package.json") == null && parent[0].findFile("elm.json") == null) {
parent[0] = parent[0].getParent();
}

Expand Down Expand Up @@ -170,25 +176,14 @@ private boolean isValidPsiFile(PsiFile file) {

@NotNull
private String createToolTip(Problems issue) {
String previousLine = "";
StringBuilder tooltip = new StringBuilder("<html><strong>" + issue.overview + "</strong><br/><hr/>");
tooltip.append("<tt>");
String[] lines = issue.details.split("\\n");
for (String line : lines) {
if (line.isEmpty()) {
continue;
}
if (!previousLine.startsWith(TAB) && line.startsWith(TAB)) {
tooltip.append("<pre style=\"font-weight:bold;\">");
} else if (previousLine.startsWith(TAB) && !line.startsWith(TAB)) {
tooltip.append("</pre>");
}
if (line.startsWith(TAB)) {
tooltip.append(line).append("\n");
} else {
tooltip.append(line).append("<br/>");
}
previousLine = line;
tooltip.append(line.replaceAll(" ", "&nbsp;"));
tooltip.append("<br/>");
}
tooltip.append("</tt>");
tooltip.append("</html>");
return tooltip.toString();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package org.elm.tools.external.elmmake;

import java.util.List;

public class CompileError {
public String path;
public String name;
public List<CompileProblem> problems;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package org.elm.tools.external.elmmake;

import java.util.List;

public class CompileErrors {
public String type;
public List<CompileError> errors;
}
10 changes: 10 additions & 0 deletions src/main/java/org/elm/tools/external/elmmake/CompileProblem.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package org.elm.tools.external.elmmake;

import java.util.List;

public class CompileProblem {
public String title;
public Region region;
public List<Object> message;
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package org.elm.tools.external.elmmake;

public class CompileProblemMessage {

}
96 changes: 72 additions & 24 deletions src/main/java/org/elm/tools/external/elmmake/ElmMake.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import com.google.gson.reflect.TypeToken;
import com.intellij.execution.ExecutionException;
import com.intellij.execution.configurations.GeneralCommandLine;
import com.intellij.execution.process.ProcessNotCreatedException;
import com.intellij.notification.Notification;
import com.intellij.notification.NotificationType;
import com.intellij.notification.Notifications;
Expand All @@ -22,7 +23,7 @@
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import java.util.Map;

public class ElmMake {
private static final Logger LOG = Logger.getInstance(ElmExternalToolsComponent.class);
Expand All @@ -34,10 +35,9 @@ private ElmMake() {
public static List<Problems> execute(String workDirectory, String nodePath, String elmMakeExePath, String file) {
GeneralCommandLine commandLine = createGeneralCommandLine(nodePath, elmMakeExePath);
commandLine.setWorkDirectory(workDirectory);
commandLine.addParameter("make");
commandLine.addParameter("--report=json");
commandLine.addParameter("--output=/dev/null");
commandLine.addParameter("--yes");
commandLine.addParameter("--warn");
commandLine.addParameter(file);

try {
Expand All @@ -46,7 +46,7 @@ public static List<Problems> execute(String workDirectory, String nodePath, Stri
Process process = commandLine.createProcess();

process.waitFor();
LOG.debug("elm-make exit value: " + process.exitValue());
LOG.debug("Elm exit value: " + process.exitValue());
if (process.exitValue() == 1) {
return parseElmMakeOutput(process.getInputStream(), process.getErrorStream());
}
Expand All @@ -63,21 +63,29 @@ public static List<Problems> parseElmMakeOutput(InputStream inputStream, InputSt
try {
List<String> lines = CharStreams.readLines(new InputStreamReader(inputStream));
if (lines.isEmpty()) {
String errorResult = new BufferedReader(new InputStreamReader(errorStream))
.lines().collect(Collectors.joining("\n"));
if (!errorResult.isEmpty()) {
String groupDisplayId = "org.elmlang.intellijplugin.elmmake";
String title = "Problem found performing Elm make";
Notification notification = new Notification(groupDisplayId, title, errorResult, NotificationType.ERROR, null);
Notifications.Bus.notify(notification);
List<String> errorLines = CharStreams.readLines(new InputStreamReader(errorStream));
if (notValidJson(errorLines)) {
String errorResult = String.join("\n", errorLines);
if (!errorResult.isEmpty()) {
String groupDisplayId = "org.elmlang.intellijplugin.elmmake";
String title = "Problem found performing Elm make";
Notification notification = new Notification(groupDisplayId, title, errorResult, NotificationType.ERROR, null);
Notifications.Bus.notify(notification);
}
} else {
for (String line : errorLines) {
List<Problems> makeResult = parseCompileErrors(line);
allProblems.addAll(makeResult);
}
}

}
for (String line : lines) {
if (notValidJsonArray(line)) {
continue;
}
output = line;
List<Problems> makeResult = parseProblemJson(output);
List<Problems> makeResult = parseProblemJsonElm018(output);
allProblems.addAll(makeResult);
}
} catch (JsonSyntaxException e) {
Expand All @@ -90,7 +98,39 @@ public static List<Problems> parseElmMakeOutput(InputStream inputStream, InputSt
return allProblems;
}

private static List<Problems> parseProblemJson(String output) {
private static List<Problems> parseCompileErrors(final String line) {
List<Problems> problems = new ArrayList<>();

final CompileErrors compileErrors = new Gson().fromJson(line, CompileErrors.class);
for (final CompileError error : compileErrors.errors) {
for (CompileProblem compileProblem : error.problems) {
StringBuilder details = new StringBuilder();
for (Object message : compileProblem.message) {
if (message instanceof String) {
details.append(message);
} else if (message instanceof Map) {
final Map messageMap = (Map) message;
messageMap.get("bold");
messageMap.get("underline");
messageMap.get("color");
final String string = (String) messageMap.get("string");
details.append(string);
}
}

final Problems problem = new Problems();
problem.file = error.path;
problem.type = compileErrors.type;
problem.region = compileProblem.region;
problem.overview = compileProblem.title;
problem.details = details.toString();
problems.add(problem);
}
}
return problems;
}

private static List<Problems> parseProblemJsonElm018(String output) {
return new Gson().fromJson(output, new TypeToken<List<Problems>>() {
}.getType());
}
Expand All @@ -99,29 +139,37 @@ private static boolean notValidJsonArray(String line) {
return !line.startsWith("[");
}

private static boolean notValidJson(List<String> errorLines) {
return !(!errorLines.isEmpty() && errorLines.get(0).startsWith("{"));
}

public static String getVersion(final String nodePath, String elmMakeExePath) throws ExecutionException, InterruptedException, IOException {
GeneralCommandLine commandLine = createGeneralCommandLine(nodePath, elmMakeExePath);
commandLine.addParameter("--help");
Process process = commandLine.createProcess();
process.waitFor();
if (process.exitValue() == 0) {
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
return bufferedReader.readLine();
} else {
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(process.getErrorStream()));
throw new IllegalStateException(bufferedReader.readLine());
commandLine.addParameter("--version");
try {
Process process = commandLine.createProcess();
process.waitFor();
if (process.exitValue() == 0) {
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
return bufferedReader.readLine();
} else {
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(process.getErrorStream()));
throw new IllegalStateException(bufferedReader.readLine());
}
} catch (ProcessNotCreatedException e) {
return "-";
}
}

public static boolean fileStartsWithShebang(final String elmMakeExePath) {
if (elmMakeExePath.isEmpty() || !new File(elmMakeExePath).exists()) {
return false;
}
try (BufferedReader bufferedReader = new BufferedReader(new FileReader(elmMakeExePath))) {
try (BufferedReader bufferedReader = new BufferedReader(new FileReader(new File(elmMakeExePath)))) {
final String firstLine = bufferedReader.readLine();
return firstLine.startsWith("#!");
} catch (IOException e) {
LOG.error("Could not read elm-make: " + elmMakeExePath);
// Ignored
}
return false;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,16 +32,16 @@
</hspacer>
</children>
</grid>
<component id="f89d8" class="javax.swing.JLabel" binding="elmMakeExeLabel">
<component id="f89d8" class="javax.swing.JLabel" binding="elmExeLabel">
<constraints>
<grid row="1" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="4" fill="0" indent="1" use-parent-layout="false"/>
</constraints>
<properties>
<text value="Elm-make executable"/>
<toolTipText value="Path to the scss-lint exe"/>
<text value="Elm executable"/>
<toolTipText value="Path to the Elm executable"/>
</properties>
</component>
<component id="31d32" class="com.intellij.ui.TextFieldWithHistoryWithBrowseButton" binding="elmMakeField">
<component id="31d32" class="com.intellij.ui.TextFieldWithHistoryWithBrowseButton" binding="elmExeField">
<constraints>
<grid row="1" column="1" row-span="1" col-span="2" vsize-policy="0" hsize-policy="6" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
</constraints>
Expand Down Expand Up @@ -72,7 +72,7 @@
</constraints>
<properties>
<text value="Node executable"/>
<toolTipText value="Path to the scss-lint exe"/>
<toolTipText value="Path to the Node executable"/>
</properties>
</component>
</children>
Expand Down
Loading

0 comments on commit 07c4150

Please sign in to comment.