33 how-to questions if you develop a JetBrains extension

cteyton

Cédric Teyton

Posted on February 13, 2023

33 how-to questions if you develop a JetBrains extension

Are you about to start developing a JetBrains extension? Or maybe you’re currently working on that? As you may already know, once you develop a JetBrains extension, it’s available in the marketplace for all the IDEs maintained by the editor, such as JetBrains, PyCharm, Rider, WebStorm, and so on.

At Packmind, we’ve built a JetBrains extension that helps developers to share their best coding practices in their organization. We had to dive into the JetBrains SDK, and from that work, we wanted to build a ‘how-to’ list of use cases that might be helpful for you. We wish we had such one when we started working on that extension! Of course, feel free to ask any more questions! 🙂

NB: We also produced a similar post for VSCode if you consider a VSCode extension.

ℹ️ All the Java imports have not been included for readability purposes. Still, we added some of them to ensure you’re on the right way ;)

#1 How to access the visible lines in the current editor?

What if you’d be interested in computing stuff only for the content in the visible screen?

FileEditorManager fileEditorManager = FileEditorManager.getInstance(project);
Editor editor = fileEditorManager.getSelectedTextEditor(); 
// Instantiate the editor above as it suits the most for you

Document document = editor.getDocument();
Rectangle visibleArea = editor.getVisibleArea();

int startLine = document.getLineNumber(visibleArea.y);
int endLine = document.getLineNumber(visibleArea.y + visibleArea.height);

for (int line = startLine; line <= endLine; line++) {
    int startOffset = document.getLineStartOffset(line);
    int endOffset = document.getLineEndOffset(line);
    String lineText = document.getText(new TextRange(startOffset, endOffset));
    //Do smth with it, add to a list, or concatenate to a string.
}
Enter fullscreen mode Exit fullscreen mode

#2 How to get the selected text?

Here're two examples of how to get the selected text in the editor:

Editor editor = FileEditorManager.getInstance(project).getSelectedTextEditor();;
SelectionModel selectionModel = editor.getSelectionModel();

if (selectionModel.hasSelection()) {
    String selectedText = selectionModel.getSelectedText();
    // do something
}

// Alternative
if (selectionModel.hasSelection()) {
    int start = selectionModel.getSelectionStart();
    int end = selectionModel.getSelectionEnd();
    String selectedText = editor.getDocument().getText(new TextRange(start, end));
    // do something
}
Enter fullscreen mode Exit fullscreen mode

#3 How to add a marker to a line with a custom image?

You’ll need to extend the GutterIconRenderer class.

import com.intellij.openapi.editor.markup.GutterIconRenderer;
// ...
Editor editor = ...;
int lineNumber = ...; // the line number where you want to add a gutter

Icon icon = IconLoader.getIcon("/icons/youricon.png");
GutterIconRenderer renderer = new GutterIconRenderer() {
    @Override
    public Icon getIcon() {
        return icon;
    }

    @Override
    public boolean equals(Object obj) {
        return obj instanceof GutterIconRenderer && ((GutterIconRenderer) obj).getIcon() == icon;
    }
};

EditorGutter gutter = ((EditorEx) editor).getGutter();
gutter.registerTextAnnotation(lineNumber, renderer);
Enter fullscreen mode Exit fullscreen mode

Override the equals method ensures that the marker won't be duplicated when the line is repainted.

#4 How to get the programming language of the current editor?

Easy one when you have access to the current Document:

import com.intellij.openapi.fileTypes.FileType;
//...
Editor editor = ...;
FileType fileType = ((EditorEx) editor).getDocument().getFileType();
String language = fileType.getName();
Enter fullscreen mode Exit fullscreen mode

#5 How to add a menu on the right-click event?

To add a menu to the right-click event, you need to create a custom AnAction :

import com.intellij.openapi.actionSystem.AnAction;
//... 
public class MyMenuAction extends AnAction {
    public MyMenuAction() {
        super("My Menu Action");
    }

    @Override
    public void actionPerformed(AnActionEvent e) {
        // Your implementation here
    }
}
Enter fullscreen mode Exit fullscreen mode

And register it as a popup menu action:

import com.intellij.openapi.actionSystem.ActionManager
//...
ActionManager actionManager = ActionManager.getInstance();
actionManager.registerAction("myMenuActionId", new MyMenuAction());
Enter fullscreen mode Exit fullscreen mode

#6 How to set a custom shortcut to perform an action?

Following tip #5, declare first an AnAction class. Then we will use the Keymap class to add a shortcut to the action:

import com.intellij.openapi.keymap.Keymap;
//....
Keymap keymap = KeymapManager.getInstance().getActiveKeymap();
String actionId = "myActionId";
MyAction action = new MyAction();
keymap.addShortcut(actionId, KeyStroke.getKeyStroke(KeyEvent.VK_1, InputEvent.CTRL_DOWN_MASK)); // Ctrl+1
Enter fullscreen mode Exit fullscreen mode

#7 How to list the JetBrains extensions pane opened in the IDE?

If your extension needs to know that information, here you are:

import com.intellij.openapi.wm.ToolWindowManager;
//...
ToolWindowManager toolWindowManager = ToolWindowManager.getInstance(project);
for (String id: toolWindowManager.getToolWindowIds()) {
    ToolWindow toolWindow = toolWindowManager.getToolWindow(id);
    if (toolWindow != null && toolWindow.isVisible()) {
        System.out.println("Opened tool window: " + id);
    }
}
Enter fullscreen mode Exit fullscreen mode

The isVisible method determines if the tool window is currently open.

#8 How to retrieve all the JetBrains extensions installed?

This information can be complementary to the previous tip #7:

import com.intellij.openapi.extensions.ExtensionPoint;
// ...
ExtensionPoint<PluginDescriptor> extensionPoint = Extensions.getRootArea().getExtensionPoint(Extensions.getExtensionPointName("com.intellij.pluginsList"));
for (PluginDescriptor pluginDescriptor : extensionPoint.getExtensionList()) {
    System.out.println("Installed plugin: " + pluginDescriptor.getName());
}
Enter fullscreen mode Exit fullscreen mode

#9 How to underline a line of code?

You can use the EditorGutterComponent class and the GutterIconRenderer class:

import com.intellij.openapi.editor.markup.GutterIconRenderer;
import com.intellij.codeInsight.daemon.LineMarkerInfo;
//...
public class MyLineMarkerProvider extends LineMarkerProvider {
    @Nullable
    @Override
    public LineMarkerInfo getLineMarkerInfo(@NotNull PsiElement element) {
        if (element instanceof PsiMethodCallExpression) {
            int startOffset = element.getTextRange().getStartOffset();
            int endOffset = element.getTextRange().getEndOffset();
            TextRange textRange = new TextRange(startOffset, endOffset);
            GutterIconRenderer gutterIconRenderer = new MyGutterIconRenderer();
            return new LineMarkerInfo<>(element, textRange, null, Pass.UPDATE_ALL, null, gutterIconRenderer, GutterIconRenderer.Alignment.RIGHT);
        }
        return null;
    }
}
Enter fullscreen mode Exit fullscreen mode

And this is the GutterIconRenderer implementation, where we took as an example the notification icon, but you’ll be welcome to add your icon:

private static class MyGutterIconRenderer extends GutterIconRenderer {
    @NotNull
    @Override
    public Icon getIcon() {
        return AllIcons.Ide.Notification.Info;
    }
}
Enter fullscreen mode Exit fullscreen mode

#10 How to retrieve the file name of the current tab?

Here you are:

import com.intellij.openapi.vfs.VirtualFile;
//...
FileEditorManager fileEditorManager = FileEditorManager.getInstance(project);
Editor editor = fileEditorManager.getSelectedTextEditor();
if (editor != null) {
    VirtualFile virtualFile = FileEditorManagerEx.getInstanceEx(project).getFile(editor);
    if (virtualFile != null) {
        System.out.println("Current file name: " + virtualFile.getName());
    }
}
Enter fullscreen mode Exit fullscreen mode

Note that the FileEditorManagerEx class provides additional functionality for managing editor tabs.

#11 How to listen when a tab has been closed?

You can implement a listener for when a tab is closed:

FileEditorManager.getInstance(project).addFileEditorManagerListener(new FileEditorManagerListener() {
    @Override
    public void fileClosed(@NotNull FileEditorManager source, @NotNull VirtualFile file) {
        System.out.println("File closed: " + file.getName());
    }
});
Enter fullscreen mode Exit fullscreen mode

#12 How to listen when a tab has been opened?

In the same spirit as tip #11:

FileEditorManager.getInstance(project).addFileEditorManagerListener(new FileEditorManagerListener() {
    @Override
    public void fileOpened(@NotNull FileEditorManager source, @NotNull VirtualFile file) {
        System.out.println("File opened: " + file.getName());
    }
});
Enter fullscreen mode Exit fullscreen mode

#13 How to listen when the current tab has changed?

Again, same approach:

FileEditorManager.getInstance(project).addFileEditorManagerListener(new FileEditorManagerListener() {
    @Override
    public void selectionChanged(@NotNull FileEditorManagerEvent event) {
        VirtualFile newFile = event.getNewFile();
        if (newFile != null) {
            System.out.println("Current tab changed to: " + newFile.getName());
        }
    }
});
Enter fullscreen mode Exit fullscreen mode

#14 How to add and access a JetBrains extension setting?

The Settings class and the State class will help you. In our case in Promyze, we had to use the current user API Key:

public class PromyzeSettings {
    private static final String SETTING_KEY = "promyzeApiKey";

    @State(name = SETTING_KEY, storages = {@Storage(value = "promyzeSettings.xml")})
    public static PromyzeSettingState settingState = new PromyzeSettingState();

    public static PromyzeSettingState getInstance() {
        return ServiceManager.getService(MySettings.class).settingState;
    }
}

public class PromyzeSettingState {
    public String promyzeApiKey = "";
}
Enter fullscreen mode Exit fullscreen mode

To access the setting, you can use the following code:

String userApiKey = PromyzeSettings.getInstance().promyzeApiKey;
Enter fullscreen mode Exit fullscreen mode

#15 How to implement a Caret Listener?

This is useful if your extension needs to run an analysis when the caret moves. The CaretListener interface will be your alley:

import com.intellij.openapi.editor.CaretListener;
//...
Editor editor = FileEditorManager.getInstance(project).getSelectedTextEditor();
CaretModel caretModel = editor.getCaretModel();
caretModel.addCaretListener(new CaretListener() {
    @Override
    public void caretPositionChanged(CaretEvent e) {
        System.out.println("Caret position changed: " + e.getNewPosition());
    }
});
Enter fullscreen mode Exit fullscreen mode

The getNewPosition method of the CaretEvent class returns the new position of the caret.

#16 How to get the current line of the caret?

Here you are:

Editor editor = FileEditorManager.getInstance(project).getSelectedTextEditor();
CaretModel caretModel = editor.getCaretModel();
int lineNumber = editor.getDocument().getLineNumber(caretModel.getOffset());.
Enter fullscreen mode Exit fullscreen mode

#17 How to listen when the current editor is saved?

You’ll need to add a listener:

Editor editor = FileEditorManager.getInstance(project).getSelectedTextEditor();
Document document = editor.getDocument();
document.addDocumentListener(new DocumentListener() {
    @Override
    public void beforeDocumentChange(DocumentEvent event) {
        // Called before the text of the document is changed.
    }

    @Override
    public void documentChanged(DocumentEvent event) {
        // Called after the text of the document has been changed.
    }
});
Enter fullscreen mode Exit fullscreen mode

#18 How to listen when a specific shortcut is called?

This is how you can listen to the keyboard shortcut CTRL + X.

import com.intellij.openapi.actionSystem.KeyboardShortcut;
//...
KeyboardShortcut keyboardShortcut = new KeyboardShortcut(KeyStroke.getKeyStroke(KeyEvent.VK_X, InputEvent.CTRL_DOWN_MASK), null);
AnAction anAction = new AnAction() {
    @Override
    public void actionPerformed(AnActionEvent event) {
        // handle shortcut event
    }
};
anAction.registerCustomShortcutSet(new CustomShortcutSet(keyboardShortcut), event.getData(CommonDataKeys.EDITOR_COMPONENT));
Enter fullscreen mode Exit fullscreen mode

#19 How to change the logo of my JetBrains extension?

Add your icon to the resources directory of the project (in one of the following formats: .png, .jpeg, or .gif). Next, in the plugin.xml file, you need to specify the path to the image file in the icon attribute of the idea-plugin tag like this:

<idea-plugin>
  ...
  <icon path="icon.png"/>
  ...
</idea-plugin>
Enter fullscreen mode Exit fullscreen mode

#20 How can I display a modal form after a command is sent from a right-click menu?

Assuming you’ve implemented an action in the contextual menu, you can then display a modal form thanks to the*DialogWrapper* class:


public class MyAction extends AnAction {
  @Override
  public void actionPerformed(AnActionEvent e) {
    MyDialog dialog = new MyDialog(e.getProject());
    dialog.show();
  }
}

public class MyDialog extends DialogWrapper {
  public MyDialog(Project project) {
    super(project);
    init();
    setTitle("My Dialog");
  }

  @Nullable
  @Override
  protected JComponent createCenterPanel() {
    JPanel panel = new JPanel();
    // Add components to the panel here
    return panel;
  }
}

Enter fullscreen mode Exit fullscreen mode

#21 How to prompt a warning notification?

The Notification class will help you with that purpose:

import com.intellij.notification.Notification;
//...
Notification notification = new Notification("MyGroup", "My Title", "My Content", NotificationType.WARNING);
Notifications.Bus.notify(notification, project);
Enter fullscreen mode Exit fullscreen mode

On the Notification object, you can also set the setListener property to provide a listener that will be notified when the notification is clicked.

You can use as well NotificationType.INFO and NotificationType.ERROR for informative and error messages.

#22 How to get the IDE name?

As said earlier, this value can be "IntelliJ IDEA", "PyCharm", "WebStorm", etc., depending on the product being used:

import com.intellij.openapi.application.ApplicationInfo;
**//...**
String ideName = ApplicationInfo.getInstance().getFullProductName();
Enter fullscreen mode Exit fullscreen mode

#23 How to get the current version of the IDE?

Related to tip #22:

String ideVersion = ApplicationInfo.getInstance().getFullVersion();
Enter fullscreen mode Exit fullscreen mode

The full version string typically has the format "major.minor.build".

#24 How to get the current compilation issues in the opened file?

In case your extension needs them:

import com.intellij.codeInspection.ex.HighlightManager**;
//...**
FileEditorManager fileEditorManager = FileEditorManager.getInstance(project);
Editor editor = fileEditorManager.getSelectedTextEditor();
if (editor != null) {
    Document document = editor.getDocument();
    PsiFile psiFile = PsiDocumentManager.getInstance(project).getPsiFile(document);
    if (psiFile != null) {
        List<ProblemDescriptor> problems = new ArrayList<>();
        GlobalInspectionContext globalContext = InspectionManager.getInstance(project).createNewGlobalContext(false);
        for (InspectionToolWrapper toolWrapper: globalContext.getInspectionTools(psiFile)) {
            ProblemsHolder problemsHolder = new ProblemsHolder(InspectionManager.getInstance(project), document, false);
            toolWrapper.processFile(psiFile, problemsHolder, globalContext);
            problems.addAll(problemsHolder.getResults().getResultItems());
        }
        // Do something with the problems list
    }
}
Enter fullscreen mode Exit fullscreen mode

#25 How to get the content of the Output Tab?

To retrieve the output content of the app, you can use this trick:

import com.intellij.openapi.wm.ToolWindow**;
//...**
String outputContent;
ToolWindow toolWindow = ToolWindowManager.getInstance(project).getToolWindow("Output");
if (toolWindow != null) {
    Content content = toolWindow.getContentManager().getContent(0);
    JComponent component = content.getComponent();
    if (component instanceof JTextArea) {
        JTextArea textArea = (JTextArea) component;
        outputContent = textArea.getText();
    }
}
Enter fullscreen mode Exit fullscreen mode

You can see the tab name is dynamic so you can retrieve the content for another tab.

#26 How to get the current editor theme?

It might impact the way you want to render your components:

import com.intellij.openapi.editor.colors.EditorColorsManager**;**
//...
EditorColorsManager editorColorsManager = EditorColorsManager.getInstance();
EditorColorsScheme currentScheme = editorColorsManager.getGlobalScheme();
String themeName = currentScheme.getName();
Enter fullscreen mode Exit fullscreen mode

#27 How to listen when the IDE is opened?

You must create a class that implements StartupActivity and add it as an extension in your plugin.xml file:

import com.intellij.openapi.startup.StartupActivity;
// ...
public class MyStartupActivity implements StartupActivity {
  @Override
  public void runActivity(@NotNull Project project) {
    // your code here, it will be executed when the IDE is opened
  }
}
Enter fullscreen mode Exit fullscreen mode

And in your plugin.xml file:

<extensions defaultExtensionNs="com.intellij">
  <startupActivity implementation="your.package.MyStartupActivity"/>
</extensions>
Enter fullscreen mode Exit fullscreen mode

#28 How to listen when the IDE is closed?

Similar to tip #27:

public class MyApplicationListener implements ApplicationListener {
  @Override
  public void beforeApplicationQuit(@NotNull boolean isRestart) {
    // your code here, it will be executed before the IDE is closed
  }
}
Enter fullscreen mode Exit fullscreen mode

And in the plugin.xml file:

<extensions defaultExtensionNs="com.intellij">
  <applicationListener implementation="your.package.MyApplicationListener"/>
</extensions>
Enter fullscreen mode Exit fullscreen mode

#29 How to run an asynchronous task in background?

This is recommended to avoid blocking operations for the users. You can be sure they won’t hesitate to uninstall your extension if it’s too annoying ;)

You can create a class that extends Task.Backgroundable and override the run method to perform the task in the background. Here is an example:

import com.intellij.openapi.task.Task.Backgroundable;
//... 
public class MyBackgroundTask extends Task.Backgroundable {
  public MyBackgroundTask(@Nullable Project project, @NotNull String title) {
    super(project, title);
  }

  @Override
  public void run(@NotNull ProgressIndicator progressIndicator) {
    // your code here, it will be executed in the background
  }
}
Enter fullscreen mode Exit fullscreen mode

To run it:

MyBackgroundTask task = new MyBackgroundTask(project, "My Background Task");
ProgressManager.getInstance().run(task);
Enter fullscreen mode Exit fullscreen mode

#30 How to get the current language of the IDE?

Here you are:

Locale locale = Locale.getDefault();
Enter fullscreen mode Exit fullscreen mode

The language code of the locale by using the getLanguage() method:

String language = locale.getLanguage();
Enter fullscreen mode Exit fullscreen mode

#31 How to listen when an application is run?

The Run feature in your IDE:

import com.intellij.openapi.application.ApplicationListener;
import com.intellij.openapi.application.ApplicationManager;

public class MyApplicationListener implements ApplicationListener {

  public void init() {
    ApplicationManager.getApplication().addApplicationListener(this);
  }

  @Override
  public void applicationStarted() {
    // your code here, executed when the application starts
  }
}
Enter fullscreen mode Exit fullscreen mode

#32 How to listen when an Extract Method operation is called?

You can also listen for specific code operations; here, we take the Extract Method operation:

import com.intellij.refactoring.RefactoringEventListener;
import com.intellij.refactoring.RefactoringEventData;

public class MyRefactoringEventListener implements RefactoringEventListener {

  public void init() {
    RefactoringEventListener.DEFAULT.addListener(this);
  }

  @Override
  public void refactoringStarted(String refactoringId, RefactoringEventData data) {
    if (refactoringId.equals("Extract Method")) {
      // your code here, executed when the Extract Method operation is called
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

#33 How to know if the current editor has defined the "LF" or "CRLF" mode for line breaks?

For the record, LF is more on Unix/Mac, while CRLF is used on Windows. Trust me, if you need to parse the source code, it can spare you some trouble ;)

Editor editor = EditorHelper.getCurrentEditor();
if (editor != null) {
  Document document = editor.getDocument();
  String lineSeparator = document.getUserData(Document.LINE_SEPARATOR_KEY);
  if ("\n".equals(lineSeparator)) {
    System.out.println("Lf mode");
  } else if ("\r\n".equals(lineSeparator)) {
    System.out.println("crlf mode");
  }
}
Enter fullscreen mode Exit fullscreen mode

Wrapping up

That’s all, folks! We hope it can help you start working on your JetBrains extension. In case you work with your team, it can make sense to define best practices when developing a JetBrains extension. Packmind can help you with that; feel free to try it.

💖 💪 🙅 🚩
cteyton
Cédric Teyton

Posted on February 13, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related