Skip to content

Commit

Permalink
First version of the plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
jlagerweij committed Jan 14, 2018
1 parent fda6be0 commit cda4d78
Show file tree
Hide file tree
Showing 14 changed files with 794 additions and 0 deletions.
16 changes: 16 additions & 0 deletions build.gradle
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()
}

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;
}
}
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 src/main/java/org/elm/tools/external/annotator/AnnotatorFile.java
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;
}
}
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();
}

}

Loading

0 comments on commit cda4d78

Please sign in to comment.