obiwan87 / odin-intellij

Odin Support plugin for JetBrains IDEs
https://plugins.jetbrains.com/plugin/22933-odin-support
MIT License
37 stars 3 forks source link

Show errors as hyperlinks in build console #44

Closed obiwan87 closed 2 months ago

obiwan87 commented 5 months ago

Proposed Solution:

  1. Create a Console Filter Provider: Implement a ConsoleFilterProvider that will add a filter to the console output. This filter will recognize file-line patterns and convert them into clickable links.

  2. Implement the Filter: Develop a filter class that uses a regular expression to identify file-line patterns in the console output. When such a pattern is found, it will create a hyperlink that opens the corresponding file at the specified line.

  3. Register the Filter: Ensure the ConsoleFilterProvider is registered correctly in the plugin.xml file to activate the filter when the plugin is loaded.

Example Implementation:

import com.intellij.execution.filters.Filter;
import com.intellij.execution.filters.ConsoleFilterProvider;
import com.intellij.execution.filters.OpenFileHyperlinkInfo;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class MyConsoleFilterProvider implements ConsoleFilterProvider {
    @NotNull
    @Override
    public Filter[] getDefaultFilters(@NotNull Project project) {
        return new Filter[]{new MyConsoleFilter(project)};
    }
}

class MyConsoleFilter implements Filter {
    private final Project project;
    private static final Pattern FILE_LINE_PATTERN = Pattern.compile("(\\S+):(\\d+)");

    public MyConsoleFilter(Project project) {
        this.project = project;
    }

    @Nullable
    @Override
    public Result applyFilter(@NotNull String line, int entireLength) {
        Matcher matcher = FILE_LINE_PATTERN.matcher(line);
        if (matcher.find()) {
            String filePath = matcher.group(1);
            int lineNumber = Integer.parseInt(matcher.group(2)) - 1; // lines are 0-based in IntelliJ

            VirtualFile file = LocalFileSystem.getInstance().findFileByPath(filePath);
            if (file != null) {
                int startOffset = entireLength - line.length() + matcher.start();
                int endOffset = entireLength - line.length() + matcher.end();
                OpenFileHyperlinkInfo hyperlinkInfo = new OpenFileHyperlinkInfo(project, file, lineNumber);
                return new Result(startOffset, endOffset, hyperlinkInfo);
            }
        }
        return null;
    }
}

Registration in plugin.xml:

<extensions defaultExtensionNs="com.intellij">
    <consoleFilterProvider implementation="com.example.MyConsoleFilterProvider"/>
</extensions>
  1. According to ChatGPT gutters are added using the MarkupModel class.

    private void addErrorToGutter(Project project, VirtualFile file, int lineNumber, String errorMessage) {
        PsiFile psiFile = PsiManager.getInstance(project).findFile(file);
        if (psiFile == null) return;
    
        Document document = PsiDocumentManager.getInstance(project).getDocument(psiFile);
        if (document == null) return;
    
        Editor[] editors = EditorFactory.getInstance().getEditors(document, project);
        if (editors.length == 0) return;
    
        Editor editor = editors[0];
        MarkupModel markupModel = editor.getMarkupModel();
    
        RangeHighlighter highlighter = markupModel.addLineHighlighter(lineNumber, HighlighterLayer.ERROR, null);
        if (highlighter != null) {
            highlighter.setGutterIconRenderer(new GutterIconRenderer() {
                @Override
                public Icon getIcon() {
                    return AllIcons.General.Error;
                }
    
                @Nullable
                @Override
                public AnAction getClickAction() {
                    return null;
                }
    
                @Override
                public boolean equals(Object obj) {
                    return obj instanceof GutterIconRenderer;
                }
    
                @Override
                public int hashCode() {
                    return errorMessage.hashCode();
                }
    
                @Override
                public String getTooltipText() {
                    return errorMessage;
                }
            });
        }
    }

    Sources:

Dima-369 commented 2 months ago

I'm really looking forward to this!

But, I am not sure if you need to manually place anything in the gutter if you manage to fill in the errors into the Problems tab. I think they automatically get a gutter display that way, and then all problems would be nicely shown and one could quickly jump to them via the Next highlighted error action.

Dima-369 commented 2 months ago

I wonder if this code could be used: https://github.com/detekt/detekt-intellij-plugin/tree/main/src/main/kotlin/io/gitlab/arturbosch/detekt/idea/problems

Another really nice setting would be to build on save (maybe configurable in the plugin settings) and then display all build errors across all files in the Problems window.

obiwan87 commented 2 months ago

After some research I found out that you have to place the gutters yourself, yes. I will omit the gutter thing for the next release, because I didn't have time to implement it nicely. However, the hyperlinks in the console will be release with 0.5.0

Dima-369 commented 2 months ago

However, the hyperlinks in the console will be release with 0.5.0

Cool, that's a start!


Another thing about this:

Another really nice setting would be to build on save (maybe configurable in the plugin settings) and then display all build errors across all files in the Problems window.

I want to share this commit from my fork with you: https://github.com/obiwan87/odin-intellij/commit/96166af7a445afd6851d089ad89f2c359a913486

It doesn't really work well, but it might be a start. It calls odin build . --json-errors internally on the project root (attempts to every 5 seconds) and shows all errors and warnings, properly as shown in the Problems panel.

The implementation is terrible though, since I don't know how to force a re-annotation of an entire file, so it always lags behind, but is still very useful on many errors.

image

I know that https://github.com/Falsepattern/Zigbrains (IntelliJ plugin for Zig) offers the possibility to trigger a build step on save, configurable in the plugin settings. And then also proceeds to show the errors, as you see above. I tried this out a few days ago, and apparently the Zig compiler only shows 1 error on build and then aborts, never the others, unlike odin build which shows all errors luckily.

Maybe, you have an idea how to include my code into the build/run step of the Odin plugin code?

obiwan87 commented 2 months ago

Thank you! I think the way to go is to annotate the files on "build" and then - as zig does - control when the build should happen. When a file with errors is edited, ideally all errors should disappear until the next build, at least that's what I would expect.

The better way to do it, is to write your own checker (LOTS OF WORK!!), which I am doing. For now it only checks for unresolved references and works in like 95% of cases. I doesn't properly work some of the builtin types, functions and constants and with polymorphic types . In the dev branch you can see the checker in action. I'm not sure if I should ship it with 0.5.0. Maybe I'll add it, but with the possibility to turn it off in the settings.

https://github.com/user-attachments/assets/d3275d01-ac1a-42ea-a837-ef206954d33c

Dima-369 commented 2 months ago

I'm not sure if I should ship it with 0.5.0. Maybe I'll add it, but with the possibility to turn it off in the settings.

Sure, why not. If you find it useful, someone else also will 🙂

The better way to do it, is to write your own checker (LOTS OF WORK!!), which I am doing.

Yeah, you are right, but it's really an awful amount of work 😄 Especially, should anything change in the inner workings of the Odin compiler, maybe something will change with version 1.0


Meanwhile, I realized that I should have been using the ExternalAnnotator the IntelliJ SDK provides to do the error highlighting, which now works really well to show all errors. Here is the commit: https://github.com/obiwan87/odin-intellij/commit/bda4599dcb0c492ced7a2b25e768f746568e13ce

It still runs odin build . far too often and does not work for nested directories so far, but it's really nice.

https://github.com/user-attachments/assets/41894db5-c3f5-438f-b864-ea4364101a2f

obiwan87 commented 2 months ago

Very cool! Is there a possibility to not show the notification in the bottom right?

EDIT: Ah just saw that you added the notifications yourself. So it would be easy to control! :)

EDIT 2: You can get the path of the currently set SDK using com.lasagnerd.odin.sdkConfig.OdinSdkConfigPersistentState#getSdkPath(com.intellij.openapi.project.Project)

Dima-369 commented 2 months ago

Ah just saw that you added the notifications yourself. So it would be easy to control! :)

Yeah, it's just debug info for now.

You can get the path of the currently set SDK using...

That gave me an idea (implemented in https://github.com/Dima-369/odin-intellij/commit/095c7a711ebf2466690f24f90a22334a0231517d plus further commits), since those settings are on a per-project basis. I added this new setting to pick the directory to compile.

CleanShot 2024-08-12 at 22 03 44@2x

Now, https://github.com/obiwan87/odin-examples/tree/master can be compiled properly with the settings above and shows errors in other files like commons.odin as well!

So far, I am very happy with it. It just incurs a CPU hit for building so often, but so far, I did not really notice any negative effect from it on my small test projects.


Maybe you want to try it out as well with my latest changes? You might want to change this, though: private static final String EXTRA_FLAGS = "-vet -vet-cast -strict-style -max-error-count:999 -o:none";

-o:none compiles the fastest and the rest is just my personal preference.

obiwan87 commented 2 months ago

Sure, I'll include it in the next release. My main concern is also the CPU usage, especially for the bigger projects out there. But I think that this feature will add so much value while the custom checker is not done. :) if you have the time, can you please fork from develop and create a pull request? Otherwise just create a pr for master and I'll handle the merge to develop

Dima-369 commented 2 months ago

My main concern is also the CPU usage, especially for the bigger projects out there.

Definitely, but the good thing is that it is completely optional now. If the Directory to compile setting is not filled out or points to a nonexisting directory, the builds are never triggered and should not impact anyone negatively, by default.

Before creating a PR, I would like to include a Extra build flags setting and add a label above Directory to compile to add more info. I will create a PR against develop.

Dima-369 commented 2 months ago

And sorry to hijack this issue! The issue reported here: Show errors as hyperlinks in build console works in develop 🙂