33 how-to questions if you develop a JetBrains extension
Tags: programming, tutorial, productivity, webdev
Published: 2023-02-13 10:53:00

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](https://packmind.com), we’ve built a JetBrains [extension](https://plugins.jetbrains.com/plugin/15503-promyze) that helps developers to [share their best coding practices](https://www.packmind.com/create-own-best-code-practices/) in their organization. We had to dive into the [JetBrains SDK](https://plugins.jetbrains.com/docs/intellij/welcome.html), 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](https://www.promyze.com/21-questions-building-vscode-extension/) if you consider a VSCode extension.



<aside>
ℹ️ *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 ;)*

</aside>


## #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?  

```java
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.
}
```

## #2 How to get the selected text?

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

```java
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
}
```

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

You’ll need to extend the **`GutterIconRenderer`** class.

```java
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);
```

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:

```java
import com.intellij.openapi.fileTypes.FileType;
//...
Editor editor = ...;
FileType fileType = ((EditorEx) editor).getDocument().getFileType();
String language = fileType.getName();
```

## #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`** :

```java
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
    }
}
```

And register it as a popup menu action:

```java
import com.intellij.openapi.actionSystem.ActionManager
//...
ActionManager actionManager = ActionManager.getInstance();
actionManager.registerAction("myMenuActionId", new MyMenuAction());
```

## #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: 

```java
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
```

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

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

```java
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);
    }
}
```

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:

```java
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());
}
```

## #9 How to underline a line of code?

You can use the **`EditorGutterComponent`** class and the **`GutterIconRenderer`** class:

```java
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;
    }
}
```

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

```java
private static class MyGutterIconRenderer extends GutterIconRenderer {
    @NotNull
    @Override
    public Icon getIcon() {
        return AllIcons.Ide.Notification.Info;
    }
}
```

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

Here you are:

```java
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());
    }
}
```

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:

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

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

In the same spirit as tip #11:

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

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

Again, same approach:

```java
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());
        }
    }
});
```

## #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:

```java
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 = "";
}
```

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

```java
String userApiKey = PromyzeSettings.getInstance().promyzeApiKey;
```

## #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:

```java
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());
    }
});
```

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: 

```java
Editor editor = FileEditorManager.getInstance(project).getSelectedTextEditor();
CaretModel caretModel = editor.getCaretModel();
int lineNumber = editor.getDocument().getLineNumber(caretModel.getOffset());.
```

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

You’ll need to add a listener:

```java
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.
    }
});
```

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

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

```java
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));
```

## #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:

```xml
<idea-plugin>
  ...
  <icon path="icon.png"/>
  ...
</idea-plugin>
```

## #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:

```java

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;
  }
}

```

## #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);
```

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:

```java
import com.intellij.openapi.application.ApplicationInfo;
**//...**
String ideName = ApplicationInfo.getInstance().getFullProductName();
```

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

Related to tip #22:

```java
String ideVersion = ApplicationInfo.getInstance().getFullVersion();
```

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:

```java
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
    }
}
```

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

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

```java
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();
    }
}
```

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:

```java
import com.intellij.openapi.editor.colors.EditorColorsManager**;**
//...
EditorColorsManager editorColorsManager = EditorColorsManager.getInstance();
EditorColorsScheme currentScheme = editorColorsManager.getGlobalScheme();
String themeName = currentScheme.getName();
```

## #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:

```java
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
  }
}
```

And in your plugin.xml file:

```xml
<extensions defaultExtensionNs="com.intellij">
  <startupActivity implementation="your.package.MyStartupActivity"/>
</extensions>
```

## #28 How to listen when the IDE is closed?

Similar to tip #27:

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

And in the plugin.xml file:

```xml
<extensions defaultExtensionNs="com.intellij">
  <applicationListener implementation="your.package.MyApplicationListener"/>
</extensions>
```

## #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:

```java
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
  }
}
```

To run it:

```java
MyBackgroundTask task = new MyBackgroundTask(project, "My Background Task");
ProgressManager.getInstance().run(task);
```

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

Here you are:

```java
Locale locale = Locale.getDefault();
```

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

```java
String language = locale.getLanguage();
```

## #31 How to listen when an application is run?

The *Run* feature in your IDE:

```java
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
  }
}
```

## #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:

```java
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
    }
  }
}
```

## #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 ;) 

```java
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");
  }
}
```

## 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](https://packmind.com).