-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
fda6be0
commit cda4d78
Showing
14 changed files
with
794 additions
and
0 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
plugins { | ||
id "org.jetbrains.intellij" version "0.2.17" | ||
} | ||
|
||
intellij { | ||
pluginName 'elm-external-tools' | ||
version '2017.3.2' | ||
downloadSources true | ||
|
||
plugins = ['org.elm.klazuka:0.9.1'] | ||
} | ||
|
||
repositories { | ||
mavenCentral() | ||
} | ||
|
27 changes: 27 additions & 0 deletions
27
src/main/java/org/elm/tools/external/ElmExternalToolsComponent.java
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,27 @@ | ||
package org.elm.tools.external; | ||
|
||
import com.intellij.AppTopics; | ||
import com.intellij.openapi.application.ApplicationManager; | ||
import com.intellij.util.messages.MessageBus; | ||
import com.intellij.util.messages.MessageBusConnection; | ||
|
||
import org.jetbrains.annotations.NotNull; | ||
|
||
|
||
public class ElmExternalToolsComponent { | ||
private static final String COMPONENT_NAME = "Elm External Tools Component"; | ||
|
||
public void initComponent() { | ||
MessageBus bus = ApplicationManager.getApplication().getMessageBus(); | ||
MessageBusConnection connection = bus.connect(); | ||
connection.subscribe(AppTopics.FILE_DOCUMENT_SYNC, new ElmExternalToolsFileDocumentManager()); | ||
} | ||
|
||
public void disposeComponent() { | ||
} | ||
|
||
@NotNull | ||
public String getComponentName() { | ||
return COMPONENT_NAME; | ||
} | ||
} |
45 changes: 45 additions & 0 deletions
45
src/main/java/org/elm/tools/external/ElmExternalToolsFileDocumentManager.java
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,45 @@ | ||
package org.elm.tools.external; | ||
|
||
import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer; | ||
import com.intellij.openapi.editor.Document; | ||
import com.intellij.openapi.fileEditor.FileDocumentManagerAdapter; | ||
import com.intellij.openapi.project.Project; | ||
import com.intellij.openapi.project.ProjectManager; | ||
import com.intellij.psi.PsiDocumentManager; | ||
import com.intellij.psi.PsiFile; | ||
|
||
import org.jetbrains.annotations.NotNull; | ||
|
||
import static org.elm.tools.external.utils.PsiFiles.isPsiFileInProject; | ||
|
||
public class ElmExternalToolsFileDocumentManager extends FileDocumentManagerAdapter { | ||
@Override | ||
public void beforeDocumentSaving(@NotNull Document document) { | ||
super.beforeDocumentSaving(document); | ||
for (Project project : ProjectManager.getInstance().getOpenProjects()) { | ||
PsiFile psiFile = PsiDocumentManager.getInstance(project).getPsiFile(document); | ||
if (isPsiFileEligible(project, psiFile)) { | ||
processPsiFile(project, psiFile); | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* The psi files seems to be shared between projects, so we need to check if the file is physically | ||
* in that project before reformating, or else the file is formatted twice and intellij will ask to | ||
* confirm unlocking of non-project file in the other project. | ||
*/ | ||
private boolean isPsiFileEligible(Project project, PsiFile psiFile) { | ||
return psiFile != null && | ||
project.isInitialized() && | ||
!project.isDisposed() && | ||
isPsiFileInProject(project, psiFile) && | ||
psiFile.getModificationStamp() != 0; | ||
} | ||
|
||
private void processPsiFile(Project project, PsiFile psiFile) { | ||
if (psiFile.getName().endsWith(".elm")) { | ||
DaemonCodeAnalyzer.getInstance(project).restart(psiFile); | ||
} | ||
} | ||
} |
25 changes: 25 additions & 0 deletions
25
src/main/java/org/elm/tools/external/annotator/AnnotatorFile.java
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,25 @@ | ||
package org.elm.tools.external.annotator; | ||
|
||
import com.intellij.openapi.editor.Editor; | ||
import com.intellij.psi.PsiFile; | ||
|
||
public class AnnotatorFile { | ||
private final PsiFile file; | ||
private Editor editor; | ||
|
||
public AnnotatorFile(PsiFile file) { | ||
this.file = file; | ||
} | ||
|
||
public PsiFile getFile() { | ||
return file; | ||
} | ||
|
||
public void setEditor(Editor editor) { | ||
this.editor = editor; | ||
} | ||
|
||
public Editor getEditor() { | ||
return editor; | ||
} | ||
} |
193 changes: 193 additions & 0 deletions
193
src/main/java/org/elm/tools/external/annotator/ElmMakeExternalAnnotator.java
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,193 @@ | ||
package org.elm.tools.external.annotator; | ||
|
||
import com.intellij.codeInspection.ProblemHighlightType; | ||
import com.intellij.lang.annotation.Annotation; | ||
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; | ||
import com.intellij.openapi.util.text.StringUtil; | ||
import com.intellij.problems.Problem; | ||
import com.intellij.problems.WolfTheProblemSolver; | ||
import com.intellij.psi.PsiDirectory; | ||
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; | ||
import org.elm.tools.external.settings.ElmPluginSettings; | ||
import org.jetbrains.annotations.NotNull; | ||
import org.jetbrains.annotations.Nullable; | ||
|
||
import java.util.Collections; | ||
import java.util.List; | ||
import java.util.Optional; | ||
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 | ||
@Override | ||
public AnnotatorFile collectInformation(@NotNull PsiFile file, @NotNull Editor editor, boolean hasErrors) { | ||
AnnotatorFile annotatorFile = collectInformation(file); | ||
annotatorFile.setEditor(editor); | ||
return annotatorFile; | ||
} | ||
|
||
/** Called first; in our case, just return file and do nothing */ | ||
@Override | ||
@Nullable | ||
public AnnotatorFile collectInformation(@NotNull PsiFile file) { | ||
return new AnnotatorFile(file); | ||
} | ||
|
||
/** Called 2nd; look for trouble in file and return list of issues. | ||
* | ||
* For most custom languages, you would not reimplement your semantic | ||
* analyzer using PSI trees. Instead, here is where you would call out to | ||
* your custom languages compiler or interpreter to get error messages | ||
* or other bits of information you'd like to annotate the document with. | ||
*/ | ||
@Nullable | ||
@Override | ||
public List<Problems> doAnnotate(final AnnotatorFile annotatorFile) { | ||
PsiFile file = annotatorFile.getFile(); | ||
Editor editor = annotatorFile.getEditor(); | ||
|
||
ElmPluginSettings elmPluginSettings = ElmPluginSettings.getInstance(file.getProject()); | ||
|
||
if (!elmPluginSettings.pluginEnabled) { | ||
return Collections.emptyList(); | ||
} | ||
if (!isValidPsiFile(file)) { | ||
return Collections.emptyList(); | ||
} | ||
|
||
final Optional<String> basePath = findElmPackageDirectory(file); | ||
|
||
if (!basePath.isPresent()) { | ||
return Collections.emptyList(); | ||
} | ||
|
||
String canonicalPath = file.getVirtualFile().getCanonicalPath(); | ||
|
||
List<Problems> problems = ElmMake.execute(editor, basePath.get(), elmPluginSettings.elmMakeExecutable, canonicalPath); | ||
|
||
return problems | ||
.stream() | ||
.filter(res -> isIssueForCurrentFile(basePath.get(), canonicalPath, res)) | ||
.collect(Collectors.toList()); | ||
} | ||
|
||
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) { | ||
parent[0] = parent[0].getParent(); | ||
} | ||
|
||
}); | ||
|
||
if (parent[0] == null) { | ||
return Optional.empty(); | ||
} else { | ||
return Optional.ofNullable(parent[0].getVirtualFile().getCanonicalPath()); | ||
} | ||
} | ||
|
||
private boolean isIssueForCurrentFile(String basePath, String canonicalPath, Problems res) { | ||
return res.file.replace("./", basePath + "/").equals(canonicalPath); | ||
} | ||
|
||
/** Called 3rd to actually annotate the editor window */ | ||
@Override | ||
public void apply(@NotNull PsiFile file, | ||
List<Problems> issues, | ||
@NotNull AnnotationHolder holder) { | ||
Document document = PsiDocumentManager.getInstance(file.getProject()).getDocument(file); | ||
|
||
for (Problems issue : issues) { | ||
annotateForIssue(holder, document, issue, file); | ||
} | ||
} | ||
|
||
private void annotateForIssue(@NotNull AnnotationHolder holder, Document document, Problems issue, PsiFile file) { | ||
TextRange selector = findAnnotationLocation(document, issue); | ||
|
||
Annotation annotation; | ||
if (issue.type.equals("warning")) { | ||
if (issue.tag.equals("unused import")) { | ||
holder.createWeakWarningAnnotation(selector, null); | ||
annotation = holder.createWeakWarningAnnotation(selector, issue.overview); | ||
annotation.setHighlightType(ProblemHighlightType.LIKE_UNUSED_SYMBOL); | ||
} else { | ||
annotation = holder.createWarningAnnotation(selector, issue.overview); | ||
} | ||
} else { | ||
annotation = holder.createErrorAnnotation(selector, issue.overview); | ||
|
||
WolfTheProblemSolver theProblemSolver = WolfTheProblemSolver.getInstance(file.getProject()); | ||
final Problem problem = theProblemSolver.convertToProblem(file.getVirtualFile(), issue.region.start.line, issue.region.start.column, new String[]{ issue.details }); | ||
theProblemSolver.weHaveGotNonIgnorableProblems(file.getVirtualFile(), Collections.singletonList(problem)); | ||
} | ||
|
||
String tooltip = createToolTip(issue); | ||
annotation.setTooltip(tooltip); | ||
} | ||
|
||
@NotNull | ||
private TextRange findAnnotationLocation(Document document, Problems issue) { | ||
Region region = issue.subregion != null ? issue.subregion : issue.region; | ||
|
||
int offsetStart = StringUtil.lineColToOffset(document.getText(), region.start.line - 1, region.start.column - 1); | ||
int offsetEnd = StringUtil.lineColToOffset(document.getText(), region.end.line - 1, region.end.column - 1); | ||
|
||
if (isMultiLineRegion(region)) { | ||
offsetEnd = document.getLineEndOffset(region.start.line - 1); | ||
} | ||
return new TextRange(offsetStart, offsetEnd); | ||
} | ||
|
||
private boolean isMultiLineRegion(Region region) { | ||
return region.start.line != region.end.line; | ||
} | ||
|
||
private boolean isValidPsiFile(PsiFile file) { | ||
return file != null && file.getVirtualFile() != null && file.getVirtualFile().isValid(); | ||
} | ||
|
||
@NotNull | ||
private String createToolTip(Problems issue) { | ||
String previousLine = ""; | ||
StringBuilder tooltip = new StringBuilder("<html><strong>" + issue.overview + "</strong><br/><hr/>"); | ||
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("</html>"); | ||
return tooltip.toString(); | ||
} | ||
|
||
} | ||
|
Oops, something went wrong.