LineMarker for PsiElement, no target

Hello,

I'm trying to add a gutter icon to the line that contains a specific PsiElement. I want the gutter icon attached to the line position of the element because if the user edits the file and the element ends up on a different line, I want the gutter icon to move to the line on which the element is on.

I've read through the LineMarkerProvider guide and it seems to be similar to what I want to achieve except I don't have a target for the marker. And the target of the marker can't be null, so using the navigationGutterIconBuilder won't work.

Is there any way to achieve what I'm trying to do?

0
19 comments
Avatar
Permanently deleted user

You don't need the NavigationGutterIconBuilder.  You create the LineMarkerInfo<> for the PsiElement you want the marker attached to.  (Or the smallest PsiElement that is associated, like the name of the method, if a method is being marked.)

You can find an example here: https://github.com/HaxeFoundation/intellij-haxe/blob/master/src/common/com/intellij/plugins/haxe/ide/HaxeLineMarkerProvider.java

 

0
Avatar
Permanently deleted user

Thanks Eric! 

Another question: I'm trying to add the LineMarkerInfo object to the result Collection parameter in the function:

collectNavigationMarkers(@NotNull PsiElement element, Collection<? super RelatedItemLineMarkerInfo> result)

but I run into two different issues depending on how I try to add the Markers.

 

1. the compiler won't allow "new LineMarkerInfo<>" to have empty carets but putting a type in causes an error.

2. 
trying to add the results this way causes the type error shown above.

 

I've gone over generics and what confuses me is that LineMarkerInfo isn't a subclass of RelatedItemLineMarker so I'm not even sure how the example here doesn't cause a type error.

 

0
Avatar
Permanently deleted user

Hmm.  I hate to leave you hanging, but I'm as lost as you are at the moment.  LineMarkerInfo is definitely a super-class of RelatedItemLineMarkerInfo.

0
Avatar
Permanently deleted user

I figured out what was going wrong:

createLineMarkerInfo() in NavigationGutterIconBuilder returns a RelatedItemLineMarkerInfo, so you have to create a RelatedItemLineMarkerInfo.

I don't know why you can't add a LineMarkerInfo obj to the collection but by using RelatedItemLineMarkerInfo it works.

 

On another note: When does collectNavigationMarkers() get called?

0
Avatar
Permanently deleted user

During the highlighting pass, it looks like.

0
Avatar
Permanently deleted user

I put print debugging statements in the function and it appears that the function is never being called because I don't see those lines in the console.

I registered the lineMarkerProvider like so:

isn't that all I should need for linking my custom provider?

0
Avatar
Permanently deleted user

The language on the codeInsight.lineMarkerProvider tag must match the language of the file being edited (marked).  You are trying to add markings to python sources, correct?  In that case, you should be using `language="Python"`.

In addition, the implementation class must be the complete class path name (e.g. `com.intellij.plugins.earthworm.Highlighter.EarthwormHighlighterManager`, if I guess your file hierarchy correctly).

0
Avatar
Permanently deleted user

Thanks the provider actually gets registered and collectNavigationMarkers() is called.

 

The issue I'm now running into is that in some runs of my plugin sometimes my linemarkers appear and other times they don't.

There are two different scenarios that occur:

I run my program, click a custom menu button that adds information for a line marker to be created when CollectNavigationMarkers() is called, then either:

  1. CollectNavigationMarkers() gets called. Then the linemarker's icon appears in the gutter and at the line where it's expected to be. When the icon is clicked there appears a drop-down scroll menu of many duplicates of the one linemarker. So it appears            collectNavigationMarkers() just keeps creating and adding the same linemarker, which makes sense because I never clear the current linemarkers. How can adding duplicates be avoided?
  2. CollectNavigationMarkers() doesn't get called for some reason.The linemarker doesn't appear. I'm not sure why CollectNavigationMarkers() wouldn't get called when a new linemarker gets added

 

0
Avatar
Permanently deleted user

For 1), You can ensure that the marker isn't already created by getting the existing markers via LineMarkersPass.queryLineMarkers(,,,), and then checking whether the marker is already in that list.  But it seems like IDEA should already be doing that.  Make sure that the linemarkers for the same element are equals().  Also, be sure that you are not creating separate markers for parent elements; they should only be created for leaf elements (e.g. an element with no children, such as the ID).

For 2)  I think CollectNavigationMarkers() only gets called when the file changes, or something else causes a highlighting pass to occur.  I don't know if it will work, but it looks like DaemonCodeAnalyzer.getInstance(project).getFileStatusMap().markFileScopeDirty(...) will do the trick.

1
Avatar
Permanently deleted user

1) Any idea as to why queryLineMarkers() would cause a stack overflow error when there aren't any linemarkers currently.

PsiFile f = element.getContainingFile();
System.out.println(f.getVirtualFile().getName());
Project p = ProjectManager.getInstance().getOpenProjects()[0];
Document d = PsiDocumentManager.getInstance(p).getDocument(f);


Collection<LineMarkerInfo> marks = LineMarkersPass.queryLineMarkers(f, d);

 

2) I tried putting these two lines after I create the information needed to make my line markers:


DaemonCodeAnalyzer d = DaemonCodeAnalyzer.getInstance(p);
((DaemonCodeAnalyzerImpl)d).getFileStatusMap().markFileUpToDate(document, Pass.UPDATE_ALL);

but this doesn't cause collectNavigationMarkers() to be called, so I don't think that's an option

0
Avatar
Permanently deleted user

> 1) Any idea as to why queryLineMarkers() would cause a stack overflow error when there aren't any linemarkers currently.

 

Haha, sorry. I didn't read enough code, I guess.  LineMarkersPass.queryLineMarkers() calls doCollectInformation(), which calls queryProviders(), which calls... you guessed it... collectSlowLineMarkers().

DaemonCodeAnalyzer.getLineMarkers(document, project) queries the document model, rather than using a line marker pass.  Or, if you want to optimize your contributor, you can use LineMarkersUtil.processLineMarkers() -- which is what DaemonCodeAnalyzer uses -- yourself with a range and your own callback.

 

> 2) ...markFileUpToDate... doesn't cause collectNavigationMarkers() to be called...

 

Right. That does the opposite of what you want; it tells IDEA that there have been no changes, so no highlighting pass needs to be run.  Did you try markFileScopeDirty()?

 

1
Avatar
Permanently deleted user

1) I'll try that out and see what happens, thanks!

 

2) markFileScopeDirty isn't a public function so I can't access it within my file

0
Avatar
Permanently deleted user

Chasing markFileScopeDirty() up through its callers gets us DaemonCodeAnalyzer.restart(File). 

0
Avatar
Permanently deleted user

Great, the linemarker is showing up now every time I run the plugin

I still am trying to find a way to check why duplicate linemarkers are being made. I'll look more into why the linemarkers aren't being considered equivalent. Currently I'm only creating one linemarker so all of the duplicates should have the exact same values

0
Avatar
Permanently deleted user

RelatedLineMarkerInfo and it's superclasses don't seem to have an implemented equals() method, so that's why it keeps adding duplicate linemarkers to the result collection despite the linemarkers having the same fields.

Is the only reasonable solution to create my own method to check if the collection contains a linemarker exactly like the one that's trying to be added? 

0
Avatar
Permanently deleted user

It looks like there isn't a reasonable way of comparing RelatedLineMarkerInfo objects since there isn't a way to properly compare the NavHandlers, alignment, targets, or text range.

0
Avatar
Permanently deleted user

Well, if you are creating the RelatedLineMarkerInfo (e.g. your code is calling `new RelatedLineMarkerInfo`, then simply sub-class it and implement `hashcode()` and `equals()` (IDEA can generate the code for you).

If you don't want to query the collection, you can also add a user data to the element that gets the line marker.  The data will go away when the PsiElement is no longer part of the file, but then you can easily implement LineMarkerProvider.getLineMarker(Element), and then call it when you need to know if there's already a marker on it.  Something like this:

static final Key<RelatedLineMarkerInfo> EARTHWORM_MARKER = new Key<>("Earthworm.Linemarker");

void putMarker(@NotNull PsiElement element, @NotNull RelatedLineMarkerInfo marker) {
element.putUserData(EARTHWORM_MARKER, marker);
}

@Override
LineMarkerInfo getLineMarkerInfo(@NotNull Psielement element) {
return element.getUserData(EARTHWORM_MARKER);
}



 

0
Avatar
Permanently deleted user

hmm okay I see what you mean and I think that leads me to my next question:

I want to be able to attach linemarkers to multiple PsiElements in the document, and have them be independent of each other. I've made it so you can click on the individual markers and have them removed, but when you delete any of the PsiElements from the text of the file weird behavior happens if the markers are on the same type of PsiElement.

For example if there are 3 markers all attached to Python's Py:DEF_KEYWORD but on separate lines and you delete any of the "def" keywords, any or all of the other markers will disappear

Is there any way that I could have PsiElements be treated as separate entities?

0
Avatar
Permanently deleted user

All PsiElements are separate entities.  What you are probably seeing is that edits to the file are invalidating the later elements (they are parsed as errors, or they are in some section that must be re-parsed -- determined based upon the lexer's internal state).  When later elements are invalid or change type, even momentarily, they are removed and their markers and user data is also removed.  So, the way to get the elements to stay is, effectively, by not editing the file. :/ 

Another possibility, if you are in control of the lexing and parsing, is to closely watch what is happening in the lexer and parser when edits are being made, and then limit the state changes and range of incremental parsing, thus reducing the PsiDummyBlocks that are created when the parser detects an error state.

If I were you, I'd also take a look at how other markers (e.g. field overrides) are handled when edits happen.  Do they also disappear?  If you see other markers behaving differently, take a look at their implementations and see how they recover quickly.

0

Please sign in to leave a comment.