[JAVA] I made a plugin for IntelliJ IDEA

This article "DeNA IP Platform Division Advent Calendar 2017" This is the article on the 19th day.

In my team, all server-side engineers are coding using IntelliJ IDEA. Recently, I suddenly wondered "How do I make a plug-in for IntelliJ IDEA?", And while studying, I tried to make something like a desktop mascot that appears on the IDE, so I'd like to summarize the flow.

What I made

When you start the IDE, strange creatures will appear. Experience points (Exp) will accumulate as you code. (I wanted to add a function that supports coding a little more, but that's okay) movie.gif

Environment

You need IntelliJ IDEA to make a plugin. (Community Edition is also acceptable) It seems that debugging during plug-in development will be easier if the source code of IntelliJ IDEA Community Edition is also dropped, but I did not use it this time. The detailed procedure is described in Official document, so I will omit it.

I will try to make it for the time being

New project

[File> New Project> IntelliJ Platform Plugin> Next] on IntelliJ IDEA Give the project name an appropriate name. (This time is Nunyu) new_project1.png

Make a component

The base of the plug-in is the following three types of components.

--Application level components: Components that are initialized when the IDE starts --Project level components: Components created for each project on the IDE --Module level components: Components created for each module on the IDE

This time, I want the mascot to appear all the time across the project when the IDE starts, so I will create it with the Application level component. You can create a new one by right-clicking on the Project panel and selecting [New> Plugin DevKit> Application Component]. The component name is "Nunyu". new_component.png

Make sure it works

In fact, the plugin is now running on the IDE! Component initialization process Let's log and run it with ʻinitComponent`.

src/Nunyu.java


import com.intellij.openapi.diagnostic.Logger;
    ...
    @Override
    public void initComponent() {
        Logger.getInstance(Nunyu.class).info("Nunyu");
    }
    ...

To execute it, select [Run> Debug] from the menu. Then another IntelliJ IDEA will be launched and the plug-in will run in it. And you can see that there is a message in the debug console. debug.png

Try to display the mascot

Bring up JWindow

Let the component inherit JWindow to display the window at initialization and render the image. This is the story of Swing.

src/Nunyu.java


import com.intellij.openapi.components.ApplicationComponent;
import org.jetbrains.annotations.NotNull;

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;

public class Nunyu extends JWindow implements ApplicationComponent {
    private Image image;
    private ImagePanel imagePanel;

    public Nunyu() {
    }

    @Override
    public void initComponent() {
        initWindow();
        makeDraggable();
    }

    public void initWindow() {
        image = Toolkit.getDefaultToolkit().getImage(getClass().getResource("/images/nunyu-alpha.png "));

        setSize(100, 100);
        setAlwaysOnTop(true);
        setBackground(new Color(1, 0, 0, 0));

        imagePanel = new ImagePanel();
        add(imagePanel);

        setVisible(true);
    }
    
    public void makeDraggable() {
        final Point startPos = new Point();
        addMouseListener(new MouseAdapter() {
            @Override
            public void mousePressed(MouseEvent e) {
                startPos.setLocation(e.getPoint());
            }
        });
        addMouseMotionListener(new MouseMotionAdapter() {
            @Override
            public void mouseDragged(MouseEvent e) {
                setLocation(e.getXOnScreen() - startPos.x, e.getYOnScreen() - startPos.y);
            }
        }); 
    }

    public class ImagePanel extends JPanel {
        public ImagePanel() {
            setOpaque(false);
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            g.drawImage(image, 0, 0, this);
        }

    }

    @Override
    public void disposeComponent() {
        // TODO: insert component disposal logic here
    }

    @Override
    @NotNull
    public String getComponentName() {
        return "Nunyu";
    }
}

--I want the background to be missing like a mascot, so use setBackground (new Color (0, 0, 0, 0)) to make the window transparent. --Prepare transparent PNG in resources / images / nunyu-alpha.png, set it tosetOpaque (false)and render it. --I want you to always display it at the top, so setAlwaysOnTop (true) --I want to be able to drag the mascot, so look at the mouse event and implement it a bit makeDraggable ()

nunyu-alpha.png

Now when you run it, you'll see a mascot that you can drag! show1.png

Hide mascot when IDE is inactive

At this rate, even if the IDE loses focus, the mascot will remain displayed at the top and will not disappear. It's annoying. So, when the IDE loses focus, I will add a process to erase the mascot at the same time.

src/Nunyu.java


    @Override
    public void initComponent() {
        initWindow();
        setupFocusEvent();
    }
    ...
    public void setupFocusEvent() {
        WindowManager.getInstance().addListener(new WindowManagerListener() {
            @Override
            public void frameCreated(IdeFrame frame) {
                WindowManager.getInstance().getFrame(frame.getProject()).addWindowFocusListener(new WindowFocusListener() {
                    @Override
                    public void windowGainedFocus(WindowEvent e) {
                        setVisible(true);
                    }

                    @Override
                    public void windowLostFocus(WindowEvent e) {
                        if (e.getOppositeWindow() == null) {
                            setVisible(false);
                        }
                    }
                });
            }

            @Override
            public void beforeFrameReleased(IdeFrame frame) {
            }
        });
    }

Add a WindowManagerListener listener and add a WindowFocusListener to that window each time a frame is created in the IDE. In this listener, implement windowGainedFocus, which fires when the window gets focus, and windowLostFocus, which fires when the window loses focus. I want to display the mascot when the window on the IDE gets focus, so I can call setVisible (true). Conversely, if you lose focus, you can use ʻe.getOppositeWindow ()to get the window that will be the next focus, but if this isnull, the IDE will not have focus, so setVisible. Call (false) `.

Give experience points to the mascot

Hook editor keystrokes

Now, next time I code in the editor, I will try to give the mascot experience points. This requires hooking editor keystrokes, but let's extend ʻeditorTypedHandler` to achieve this.

src/MyTypedHandler.java


import com.intellij.codeInsight.editorActions.AutoFormatTypedHandler;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.actionSystem.TypedActionHandler;
import org.jetbrains.annotations.NotNull;

public class MyTypedHandler extends AutoFormatTypedHandler {
    public MyTypedHandler(TypedActionHandler originalHandler) {
        super(originalHandler);
    }

    @Override
    public void execute(@NotNull Editor editor, char charTyped, @NotNull DataContext dataContext) {
        super.execute(editor, charTyped, dataContext);
        Logger.getInstance(Nunyu.class).info("Typed: " + charTyped);
    }
}

resources/META-INF/plugin.xml


  ...
  <extensions defaultExtensionNs="com.intellij">
    <editorTypedHandler implementationClass="MyTypedHandler"/>
  </extensions>
  ...

When any character is typed in the editor, ʻexecute ()is called. Try to output the pressed key to the log while callingsuper.execute ()as it is so as not to change the default behavior. Try running it now and edit something on the IDE that launches. You can see that the key you entered appears in the info log. This is how to hook this key, but it seems like [There is also a way to useTypedHandlerDelegate`](https://qiita.com/shiraji/items/5a7a1654dc7b6da20a60), but when I tried it, I tried it in the IDE. When AutoCompletionPopup came out, it seemed that the event would not come over, so it didn't work. (Honestly, I don't know what is the correct answer.)

Persist data

Even if the mascot can give experience points, it is sad that it is reset when the IDE is restarted. Therefore, I will try to make it possible to perpetuate the experience value given next. Use PersistentStateComponent to persist data in the plugin.

src/MyService.java


import com.intellij.openapi.components.PersistentStateComponent;
import com.intellij.openapi.components.ServiceManager;
import com.intellij.openapi.components.State;
import com.intellij.openapi.components.Storage;

@State(
        name = "MyService",
        storages = {
                @Storage("nunyu.xml"),
        }
)

class MyService implements PersistentStateComponent<MyService.State> {
    static class State {
        public Integer exp = 0;
    }

    State myState;

    public MyService() {
        this.myState = new State();
    }

    public State getState() {
        return myState;
    }

    public void loadState(State state) {
        myState = state;
    }

    public static MyService getInstance() {
        return ServiceManager.getService(MyService.class);
    }
}

I implemented a box that simply has the integer ʻexp. You can set the destination to be persisted with the @State` annotation.

Also, don't forget to mention in plugin.xml to use this service.

resources/META-INF/plugin.xml


  ...
  <extensions defaultExtensionNs="com.intellij">
    <applicationService serviceInterface="MyService" serviceImplementation="MyService"/>
  </extensions>
  ...

Register as ʻapplicationServiceas it will be used by components at the application level. Please note that there are otherprojectService` etc.

Connect

First of all, let's start by displaying the experience points on the screen. Rewrite the paintComponent () method of the ʻImagePanel class to display the ʻexpofMyService. Also, prepare the repaint ()` method so that you can redraw each time your experience points increase.

src/Nunyu.java


    ...
    public void repaint() {
        imagePanel.repaint();
    }
    ...
    public class ImagePanel extends JPanel {
        ...
        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            g.drawImage(image, 0, 0, this);

            g.drawString("Exp. " + MyService.getInstance().myState.exp, 0, 10);
        }
    }
    ...

Next, write a process to increase the experience value at the time of key input and redraw.

src/MyService.java


    ...
    public void increment() {
        myState.exp++;
    }
    ...

src/MyTypedHandler.java


    ...
    @Override
    public void execute(@NotNull Editor editor, char charTyped, @NotNull DataContext dataContext) {
        super.execute(editor, charTyped, dataContext);
        ServiceManager.getService(MyService.class).increment();
        ApplicationManager.getApplication().getComponent(Nunyu.class).repaint();
    }
    ...

That's it. As you code, your experience points will increase steadily!

Finally

By the way, I made a plug-in that was of no use (laughs), but by proceeding through trial and error, I somehow understood something like how to make a plug-in. It was quite difficult to find information from the documents, and I couldn't find anything like "What should I do if I want to do something like this ?!", and even such a simple one was very difficult. There are a lot of plugins on the market, so you can search for useful code from them, or Search in the community. community & topic = 200366979 & utf8 =% E2% 9C% 93) and you may or may not find the information you are looking for! This article was the end of a struggle, but if you have any suggestions, please comment.

Recommended Posts

I made a plugin for IntelliJ IDEA
I was in trouble at work, so I made a plugin for IntelliJ
I made a Diff tool for Java files
I made a Docker image of SDAPS for Japanese
I made a check tool for the release module
I made a method to ask for Premium Friday
I made a THETA API client that can be used for plug-in development
I made a chat app.
I made a library for displaying tutorials on Android.
I made a plugin to execute jextract with Gradle task
Ruby: I made a FizzBuzz program!
I made a shopify app @java
I made a GUI with Swing
I made a simple recommendation function.
I made a matching app (Android app)
I made a package.xml generation tool.
[Android] I made a pedometer app.
I made a command line interface with WinMerge Plugin using JD-Core
I made a question that can be used for a technical interview
I made a method to ask for Premium Friday (Java 8 version)
I made a reply function for the Rails Tutorial extension (Part 5):
[Ruby] I made a simple Ping client
IntelliJ IDEA Convenient function for beginners (importance ★ 4)
Made a one-liner method for Premium Friday
I made a risky die with Ruby
I made a rock-paper-scissors app with kotlin
I made a calculator app on Android
I made a new Java deployment tool
IntelliJ IDEA Convenient function for beginners (importance ★ 5)
I made a rock-paper-scissors app with android
Stop port for a certain Tomcat plug-in (8081)
I made a bulletin board using Docker 1
About Stream Debugger in IntelliJ IDEA plugin
I made a reply function for Rails Tutorial extension (Part 2): Change model
I made a primality test program in Java
04. I made a front end with SpringBoot + Thymeleaf
I made a mosaic art with Pokemon images
I made an app for myself! (Reading management app)
I made an Android app for MiRm service
I made a rock-paper-scissors game in Java (CLI)
I made a viewer app that displays a PDF
I made a Docker container to run Maven
I made a Ruby extension library in C
I made an API client for Nature Remo
I made a simple graph library for smartphone apps [MP Android Chart Kai]
I made a LINE bot with Rails + heroku
IntelliJ IDEA settings
I tried using the profiler of IntelliJ IDEA
I made a portfolio with Ruby On Rails
A memorandum because I was addicted to the setting of the Android project of IntelliJ IDEA
I made a simple calculation problem game in Java
[Ruby] I made a crawler with anemone and nokogiri.
I tried the new feature profiler of IntelliJ IDEA 2019.2.
I made a drawing chat "8bit paint chat" on WebAssembly
I tried installing the Docker Integration plugin in IntelliJ
I made a Restful server and client in Spring.
I made a library that works like a Safari tab !!
I made a Wrapper that calls KNP from Java
I want to create a generic annotation for a type
[Java] A technique for writing constructors, getters, and setters in one shot with IntelliJ IDEA.
I made a reply function for the Rails Tutorial extension (Part 4): A function that makes the user unique