IntelliJ Platform SDK DevGuide

Edit page

8. Line Marker Provider

Line markers help annotate code with icons on the gutter. These markers can provide navigation targets to related code.

8.1. Define a Line Marker Provider

A line marker provider annotates usages of Simple Language properties within Java code and provides navigation to the definition of these properties. The visual marker is a Simple Language icon in the gutter of the Editor window.

The Simple Language marker provider subclasses RelatedItemLineMarkerProvider. For this example, override the collectNavigationMarkers() method to collect usage of a Simple Language key and separators:

// Copyright 2000-2020 JetBrains s.r.o. and other contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package org.intellij.sdk.language; import com.intellij.codeInsight.daemon.*; import com.intellij.codeInsight.navigation.NavigationGutterIconBuilder; import com.intellij.openapi.project.Project; import com.intellij.psi.*; import com.intellij.psi.impl.source.tree.java.PsiJavaTokenImpl; import org.intellij.sdk.language.psi.SimpleProperty; import org.jetbrains.annotations.NotNull; import java.util.*; public class SimpleLineMarkerProvider extends RelatedItemLineMarkerProvider { @Override protected void collectNavigationMarkers( @NotNull PsiElement element, @NotNull Collection< ? super RelatedItemLineMarkerInfo<?> > result ) { // This must be an element with a literal expression as a parent if ( !(element instanceof PsiJavaTokenImpl) || !(element.getParent() instanceof PsiLiteralExpression) ) return; // The literal expression must start with the Simple language literal expression PsiLiteralExpression literalExpression = (PsiLiteralExpression) element.getParent(); String value = literalExpression.getValue() instanceof String ? (String) literalExpression.getValue() : null; if ( ( value == null ) || !value.startsWith( SimpleAnnotator.SIMPLE_PREFIX_STR + SimpleAnnotator.SIMPLE_SEPARATOR_STR ) ) return; // Get the Simple language property usage Project project = element.getProject(); String possibleProperties = value.substring( SimpleAnnotator.SIMPLE_PREFIX_STR.length()+ SimpleAnnotator.SIMPLE_SEPARATOR_STR.length() ); final List<SimpleProperty> properties = SimpleUtil.findProperties( project, possibleProperties ); if ( properties.size() > 0 ) { // Add the property to a collection of line marker info NavigationGutterIconBuilder< PsiElement > builder = NavigationGutterIconBuilder.create( SimpleIcons.FILE ) .setTargets( properties ) .setTooltipText( "Navigate to Simple language property" ); result.add( builder.createLineMarkerInfo( element ) ); } } }

8.2. Best Practices for Implementing Line Marker Providers

This section addresses important details about implementing a marker provider. The collectNavigationMarkers() method should:

  • Only return line marker information consistent with the element passed into the method. For example, do not return a class marker if getLineMarkerInfo() was called with an element that corresponds to a method.
  • Return line marker information for the appropriate element at the correct scope of the PSI tree. For example, do not return method marker for PsiMethod. Instead, return it for the PsiIdentifier which contains the name of the method.

Line Marker Location

What happens when a LineMarkerProvider returns marker information for a PsiElement that is a higher node in the PSI tree? For example, if MyWrongLineMarkerProvider() erroneously returns a PsiMethod instead of a PsiIdentifier element:

public class MyWrongLineMarkerProvider implements LineMarkerProvider { public LineMarkerInfo getLineMarkerInfo(@NotNull PsiElement element) { if (element instanceof PsiMethod) return new LineMarkerInfo(element, ...); return null; } }

The consequences of the MyWrongLineMarkerProvider() implementation have to do with how the IntelliJ Platform performs inspections. For performance reasons, inspection, and specifically the LineMarkersPass queries all LineMarkerProviders in two phases:

  • The first pass is for all elements visible in the Editor window,
  • The second pass is for the rest of the elements in the file.

If providers return nothing for either area, the line markers get cleared. However, if a method like actionPerformed() is not completely visible in the Editor window (as shown in the image above,) and MyWrongLineMarkerProvider() returns marker info for the PsiMethod instead of PsiIdentifier, then:

  • The first pass removes line marker info because whole PsiMethod isn’t visible.
  • The second pass tries to add a line marker because MyWrongLineMarkerProvider() is called for the PsiMethod.

As a result, the line marker icon would blink annoyingly. To fix this problem, rewrite MyWrongLineMarkerProvider to return info for PsiIdentifier instead of PsiMethod as shown below:

public class MyCorrectLineMarkerProvider implements LineMarkerProvider { public LineMarkerInfo getLineMarkerInfo(@NotNull PsiElement element) { if (element instanceof PsiIdentifier && element.getParent() instanceof PsiMethod) return new LineMarkerInfo(element, ...); return null; } }

8.3. Register the Line Marker Provider

The SimpleLineMarkerProvider implementation is registered with the IntelliJ Platform in the plugin configuration file using the com.intellij.codeInsight.lineMarkerProvider extension point.

<extensions defaultExtensionNs="com.intellij"> <codeInsight.lineMarkerProvider language="JAVA" implementationClass="org.intellij.sdk.language.SimpleLineMarkerProvider"/> </extensions>

8.4. Run the Project

Run the simple_language_plugin in a Development Instance and open the Test file. Now the icon appears next to line 3 on the gutter. A user can click on the icon to navigate to the property definition.

Line Marker

Last modified: 22 April 2020