📜 ⬆️ ⬇️

A little more about the development of plug-ins for IntelliJ

Recently, articles about creating extensions for Intellij IDE began to appear on Habré — one , and another .

I will continue this glorious trend and try to describe those Intellij OpenAPI sites that have not yet been touched; An example would be a plug-in with funny comics.


')


The extension, in fact, does just one simple thing - it displays fresh pictures from my favorite Geek & Poke in the panel , periodically downloading them from the site and caching to disk. Sources, by the way, on GitHub'e .

For especially vigilant - comics are under a good license CC BY-SA 3.0 , so everything is legal :)

Since the creation of the project itself, the writing of plugin.xml and other basic things - like the links to the relevant documentation - are already described in the above articles, we will not repeat; and I’ll just describe some of the issues I’ve come up with when developing solutions.

Proxy support


IntelliJ IDEA (and other IDEs) can connect to the network through a proxy, the configuration is described in detail in the documentation .

But to make your extension use the global IDE settings, you should look towards the com.intellij.util.net.HttpConfigurable class. Its public fields contain all the necessary information: the USE_HTTP_PROXY flag, for example, says whether we use a proxy at all or not; and there is also information about the host, port and user.

The easiest way to use the prepareURL method prepareURL to call it for each connection:
  /** * Call this function before every HTTP connection. * If system configured to use HTTP proxy, this function * checks all required parameters and ask password if * required. * @param url URL for HTTP connection * @throws IOException */ public void prepareURL (String url) throws IOException { 


For example, in the code for some url it might look like this:
  // Ensure that proxy (if any) is set up for this request. final HttpConfigurable httpConfigurable = HttpConfigurable.getInstance(); httpConfigurable.prepareURL(url.toExternalForm()); 


At the JetBrains forum, someone swears that this method does not help - but, in my opinion, they are in vain.

Starting the process when plug-in is initialized


I needed to run a separate thread that periodically checked the main page for updates.

ApplicationComponent will help. The types of components and their creation are remarkably described in the documentation, in the article Plugin Structure .

Add our component to plugin.xml :

  <application-components> <component> <implementation-class>com.abelsky.idea.geekandpoke.ComicsPlugin</implementation-class> <interface-class>com.abelsky.idea.geekandpoke.ComicsPlugin</interface-class> </component> </application-components> 


And in it we define the initComponent method:

 public class ComicsPlugin implements ApplicationComponent { private static final int UPDATE_PERIOD = 15 * 60 * 60 * 1000; //         ; //    ProjectComponent -    . @Override public void initComponent() { startUpdateTimer(); } private void startUpdateTimer() { final Timer timer = new Timer("Geek and Poke updater"); timer.schedule(new TimerTask() { @Override public void run() { //   -   15 ... } }, 0, ComicsPlugin.UPDATE_PERIOD); } 


Localization


For localization it is convenient to use the following snippet:

 package com.abelsky.idea.geekandpoke.messages; // ... public class MessageBundle { private static Reference<ResourceBundle> bundleRef; //     com/abelsky/idea/geekandpoke/messages/MessageBundle.properties // -  key-value .properties-. @NonNls private static final String BUNDLE = "com.abelsky.idea.geekandpoke.messages.MessageBundle"; private MessageBundle() { } public static String message(@PropertyKey(resourceBundle = BUNDLE)String key, Object... params) { return CommonBundle.message(getBundle(), key, params); } private static ResourceBundle getBundle() { ResourceBundle bundle = null; if (MessageBundle.bundleRef != null) { bundle = MessageBundle.bundleRef.get(); } if (bundle == null) { bundle = ResourceBundle.getBundle(BUNDLE); MessageBundle.bundleRef = new SoftReference<ResourceBundle>(bundle); } return bundle; } } 


Here it is worth paying attention to a few points.

The first is to store the ResourceBundle in the SoftReference . This is a fairly common practice in the IDEA source code - keep as many objects as possible in non-hard links.

By the way, I advise you to look at the com.intellij.reference.SoftReference class - it is the developers themselves who use it instead of the java.lang.ref implementation. The difference is that if you suspect a memory leak com.intellij.reference.SoftReference can be quickly converted into a hard-link, and this will help when profiling.

The second is the abstract of org.jetbrains.annotations.PropertyKey . It indicates that the annotated method argument can only be a string from the bundle specified in the resourceBundle parameter. Its use adds confidence that the keys in the .properties file and in the code are not synchronized (and the IDEA refactoring in the IDEA also learns a lot, as the link between the key and the bandl appears).

The third is the annotations of org.jetbrains.annotations.NonNls / org.jetbrains.annotations.Nls , marking lines that should not (or, conversely, should) be translated. Use JetBrains documentation here .

Notifications


In some events, I want to show a beautiful notice. Such, for example:



Here it is worth looking towards the com.intellij.notification.Notifications class. For example:

  private void notifyNewEntry() { final Notification newEntryNotification = new Notification( /*   */ MessageBundle.message("notification.new.strip.group"), /*  */ MessageBundle.message("notification.new.strip.title"), /*  */ MessageBundle.message("notification.new.strip.content"), NotificationType.INFORMATION); //     UI- -      invokeLater. Notifications.Bus.notify(newEntryNotification); } 


The notification group is displayed in the IDE settings, for more details see the documentation .



Settings




How to make a settings page for a plug-in, is described in detail in the documentation .

But if in brief, then, first, we register in plugin.xml :

  <extensions defaultExtensionNs="com.intellij"> <!-- ... --> <applicationConfigurable instance="com.abelsky.idea.geekandpoke.ui.SettingsPanel"/> </extensions> 


Second, we implement the interface methods com.intellij.openapi.options.Configurable . Of these, the most important - createComponent - should return the component on which our settings are displayed.

Offline cache


My plug-in needed to store pictures on the disk - in principle, the same question will arise when writing any caches that have no place in the project directory or in %TMP% . You can, for example, write them somewhere in %USERPROFILE% , or you can make it more interesting by using the directory in which the plug-in itself is installed.

Default extensions are installed in %USERPROFILE%/.IdeaIC11/config/plugins/PLUGIN_NAME ; This way, however, can be changed by setting the variable idea.plugins.path in idea.properties .

  // PLUGIN_ID -   id  plugin.xml. final PluginId id = PluginId.getId(PLUGIN_ID); final IdeaPluginDescriptor plugin = PluginManager.getPlugin(id); //    . File path = plugin.getPath(); 


The resulting path, by the way, can easily turn out to be a JAR file - if the extension is not unpacked into a separate directory.

PS


I hope this short FAQ will be useful for beginners to dig into the IntelliJ platform. And I, in the meantime, am starting to understand why I began to understand all this - an IDEA plugin to support one very interesting programming language;)

Source: https://habr.com/ru/post/150829/


All Articles