Skip to content

Commit

Permalink
feat(yakshaintellij): show compiler errors
Browse files Browse the repository at this point in the history
  • Loading branch information
JaDogg committed May 25, 2024
1 parent 77faad2 commit 28cac98
Show file tree
Hide file tree
Showing 9 changed files with 267 additions and 8 deletions.
2 changes: 1 addition & 1 deletion editor/intellij/gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
pluginGroup = com.github.jadogg.yakshaintellij
pluginName = YakshaIntelliJ
# SemVer format -> https://semver.org
pluginVersion = 0.0.12
pluginVersion = 0.0.13

# Supported build number ranges and IntelliJ Platform versions -> https://plugins.jetbrains.com/docs/intellij/build-number-ranges.html
pluginSinceBuild = 213
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ public class YakshaAnnotator implements Annotator {

@Override
public void annotate(@NotNull final PsiElement element, @NotNull AnnotationHolder holder) {

if (element instanceof YakshaDataTypeBit) {
holder.newSilentAnnotation(HighlightSeverity.INFORMATION)
.range(element.getTextRange())
Expand All @@ -35,7 +36,7 @@ public void annotate(@NotNull final PsiElement element, @NotNull AnnotationHolde
.range(fncall.getFirstChild().getTextRange())
.textAttributes(YakshaSyntaxHighlighter.KEYWORD)
.create();
if ((fullName.equals("cast") || fullName.equals("arrnew") || fullName.equals("array")) && fncall.getArgumentsList() != null
if ((fullName.equals("cast") || fullName.equals("arrnew") || fullName.equals("array") || fullName.equals("fixedarr")) && fncall.getArgumentsList() != null
&& !fncall.getArgumentsList().isEmpty() && fncall.getArgumentsList().get(0).getExpList() != null
&& fncall.getArgumentsList().get(0).getExpList().size() > 0) {
YakshaExp dt = fncall.getArgumentsList().get(0).getExpList().get(0);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
package org.intellij.sdk.language;

import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.intellij.lang.annotation.AnnotationHolder;
import com.intellij.lang.annotation.ExternalAnnotator;
import com.intellij.lang.annotation.HighlightSeverity;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.project.DumbAware;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiManager;
import org.intellij.sdk.language.psi.YakshaFile;
import org.intellij.sdk.language.tw.YakshaToolWindow;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

public class YakshaCompilerAnnotator extends ExternalAnnotator<YakshaFile, List<YakshaCompilerAnnotator.CompilerError>> implements DumbAware {
@Override
public @Nullable YakshaFile collectInformation(@NotNull PsiFile file) {
if (file instanceof YakshaFile) {
return (YakshaFile) file;
}
return null;
}

@Override
public @NotNull List<CompilerError> doAnnotate(@Nullable YakshaFile collectedInfo) {
if (collectedInfo == null) {
return List.of();
}
final var project = collectedInfo.getProject();
final var virtualFile = collectedInfo.getVirtualFile();
File mainFile = findMainFile(new File(virtualFile.getPath()), 5);
if (mainFile == null) {
return List.of();
}


return runCompiler(mainFile);
}

@Override
public void apply(@NotNull PsiFile file, List<CompilerError> annotationResult, @NotNull AnnotationHolder holder) {
if (annotationResult == null || annotationResult.isEmpty()) {
return;
}

final var document = file.getViewProvider().getDocument();
if (document == null) {
return;
}

for (CompilerError error : annotationResult) {
var line = error.line - 1;
if (line < 0 || line >= document.getLineCount()) continue;

var startOffset = document.getLineStartOffset(line) + error.column - 1;
var endOffset = startOffset + error.length;

holder.newAnnotation(HighlightSeverity.ERROR, error.message).range(new TextRange(startOffset, endOffset)).create();
}
}

private @Nullable Document getDoc(Project project, VirtualFile file) {
final var psiFile = PsiManager.getInstance(project).findFile(file);
if (psiFile == null) {
return null;
}
return PsiDocumentManager.getInstance(project).getDocument(psiFile);
}

public static class CompilerError {
final String message;
final int line;
final int column;
final int length;

private CompilerError(final String message, final int line, final int column, final int length) {
this.message = message;
this.line = line;
this.column = column;
this.length = length;
}
}

private File findMainFile(File dir, int maxDepth) {
File currentDir = dir;
for (int i = 0; i <= maxDepth; i++) {
File mainCFile = new File(currentDir, "main.yaka");
if (mainCFile.exists() && mainCFile.isFile()) {
return mainCFile;
}
currentDir = currentDir.getParentFile();
if (currentDir == null) break;
}
return null;
}

private List<CompilerError> runCompiler(File mainFile) {
List<CompilerError> errors = new ArrayList<>();
final var compiler = YakshaToolWindow.YAKSHA_EXE_PATH;
if (compiler.isBlank()) {
System.out.println("------ yaksha compiler path is not set -----");
return errors;
}

System.out.println("----- yaksha compiler: " + compiler);

List<String> command = List.of(compiler, "compile", "-d", mainFile.getAbsolutePath());

try {
Process process = new ProcessBuilder(command)
.directory(mainFile.getParentFile())
.redirectErrorStream(true)
.start();

process.waitFor(60, TimeUnit.SECONDS);

String output = new String(process.getInputStream().readAllBytes());
int exitCode = process.exitValue();

errors.addAll(parseCompilerErrors(output, mainFile.getAbsolutePath()));
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}

return errors;
}

private List<CompilerError> parseCompilerErrors(String output, String mainFilePath) {
List<CompilerError> errors = new ArrayList<>();
String[] lines = output.split("\\R");

for (String line : lines) {
if (!line.trim().isEmpty()) {
JsonObject json = JsonParser.parseString(line).getAsJsonObject();
String file = json.has("file") ? json.get("file").getAsString() : mainFilePath;
int lineNumber = json.has("line") ? json.get("line").getAsInt() : 0;
int columnNumber = json.has("pos") ? json.get("pos").getAsInt() : 0;
String message = json.get("message").getAsString();
int tokenLength = json.has("token") ? json.get("token").getAsString().length() : 1;

if (mainFilePath.equals(file)) {
errors.add(new CompilerError(message, lineNumber, columnNumber, tokenLength));
}
}
}

return errors;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package org.intellij.sdk.language.tw;

public final class ExecutableFileState {
private String executableFilePath = "";

public String getExecutableFilePath() {
return executableFilePath;
}

public void setExecutableFilePath(String executableFilePath) {
this.executableFilePath = executableFilePath;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package org.intellij.sdk.language.tw;

import com.intellij.openapi.components.PersistentStateComponent;
import com.intellij.openapi.components.Service;
import com.intellij.openapi.components.State;
import com.intellij.openapi.components.Storage;
import com.intellij.openapi.project.Project;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

@State(
name = "ExecutableFileStateService",
storages = @Storage("ExecutableFileStateService.xml")
)
@Service
public final class ExecutableFileStateService implements PersistentStateComponent<ExecutableFileState> {

private ExecutableFileState state = new ExecutableFileState();

@Nullable
@Override
public ExecutableFileState getState() {
return state;
}

@Override
public void loadState(@NotNull ExecutableFileState state) {
this.state = state;
}

public static ExecutableFileStateService getInstance(Project project) {
return project.getService(ExecutableFileStateService.class);
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="org.intellij.sdk.language.tw.YakshaToolWindow">
<grid id="27dc6" binding="myToolWindowContent" layout-manager="GridLayoutManager" row-count="2" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<grid id="27dc6" binding="myToolWindowContent" layout-manager="GridLayoutManager" row-count="3" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<margin top="0" left="0" bottom="0" right="0"/>
<constraints>
<xy x="20" y="20" width="500" height="400"/>
Expand Down Expand Up @@ -29,6 +29,14 @@
</constraints>
<properties/>
</component>
<component id="73588" class="javax.swing.JButton" binding="setYakshaCompilerPathButton" default-binding="true">
<constraints>
<grid row="2" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<text value="Set Yaksha Compiler Path"/>
</properties>
</component>
</children>
</grid>
</form>
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.intellij.sdk.language.tw;

import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.wm.ToolWindow;
import org.intellij.sdk.language.YakshaIcons;
import org.intellij.sdk.language.yaksha_docs.YakshaDocs;
Expand All @@ -11,13 +12,16 @@
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.DefaultTreeModel;
import java.awt.*;
import java.io.File;
import java.util.Locale;
import java.util.concurrent.*;

public class YakshaToolWindow {

public static String YAKSHA_EXE_PATH = "";
private JPanel myToolWindowContent;
private JTree documentationTree;
private JTextField filterText;
private JButton setYakshaCompilerPathButton;

private final Debouncer debouncer = new Debouncer();

Expand Down Expand Up @@ -47,7 +51,14 @@ public void shutdown() {
}
}

public YakshaToolWindow(ToolWindow toolWindow) {
public YakshaToolWindow(ToolWindow toolWindow, ExecutableFileStateService service) {
final var state = service.getState();
if (state != null) {
final var path = state.getExecutableFilePath();
if (path != null && !path.isBlank()) {
YAKSHA_EXE_PATH = path; // Set current path when the tool window is opened
}
}
final DefaultTreeCellRenderer renderer = new DefaultTreeCellRenderer() {
@Override
public Component getTreeCellRendererComponent(JTree tree, Object value, boolean sel, boolean expanded, boolean leaf, int row, boolean hasFocus) {
Expand Down Expand Up @@ -86,8 +97,38 @@ public void handle() {
renderTree(filterText.getText().strip());
}
});

setYakshaCompilerPathButton.addActionListener(e -> onSelectFileButtonClicked(service));
}

private void onSelectFileButtonClicked(ExecutableFileStateService service) {
JFileChooser fileChooser = new JFileChooser();
fileChooser.setDialogTitle("Select Executable File");
fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
fileChooser.setFileFilter(new javax.swing.filechooser.FileFilter() {
@Override
public boolean accept(File f) {
return f.isDirectory() || (f.isFile() && f.canExecute() && f.getName().toLowerCase(Locale.ROOT).startsWith("yaksha"));
}

@Override
public String getDescription() {
return "Yaksha executable";
}
});

int returnValue = fileChooser.showOpenDialog(myToolWindowContent);
if (returnValue == JFileChooser.APPROVE_OPTION) {
File selectedFile = fileChooser.getSelectedFile();
String filePath = selectedFile.getAbsolutePath();
assert service.getState() != null;
service.getState().setExecutableFilePath(filePath);
YAKSHA_EXE_PATH = filePath;
Messages.showMessageDialog("Set compiler path: " + filePath, "Information", Messages.getInformationIcon());
}
}


private void renderTree(String filter) {
debouncer.debounce(Void.class, () -> SwingUtilities.invokeLater(() -> {
final DefaultMutableTreeNode root = new DefaultMutableTreeNode("Root");
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
package org.intellij.sdk.language.tw;

import com.intellij.openapi.project.DumbAware;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.wm.ToolWindow;
import com.intellij.openapi.wm.ToolWindowFactory;
import com.intellij.ui.content.Content;
import com.intellij.ui.content.ContentFactory;
import org.jetbrains.annotations.NotNull;

public class YakshaToolWindowFactory implements ToolWindowFactory, DumbAware {
public class YakshaToolWindowFactory implements ToolWindowFactory {
public void createToolWindowContent(@NotNull Project project, @NotNull ToolWindow toolWindow) {
YakshaToolWindow yakshaToolWindow = new YakshaToolWindow(toolWindow);
ExecutableFileStateService service = ExecutableFileStateService.getInstance(project);
YakshaToolWindow yakshaToolWindow = new YakshaToolWindow(toolWindow, service);
ContentFactory contentFactory = ContentFactory.SERVICE.getInstance();
Content content = contentFactory.createContent(yakshaToolWindow.getContent(), "", false);
toolWindow.getContentManager().addContent(content);
Expand Down
1 change: 1 addition & 0 deletions editor/intellij/src/main/resources/META-INF/plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
<depends>com.intellij.modules.platform</depends>

<extensions defaultExtensionNs="com.intellij">
<externalAnnotator language="Yaksha" implementationClass="org.intellij.sdk.language.YakshaCompilerAnnotator"/>
<fileType
name="Yaka File"
implementationClass="org.intellij.sdk.language.YakaFileType"
Expand Down

0 comments on commit 28cac98

Please sign in to comment.