A little more about developing plugins for IntelliJ
Recently articles on creation of extensions for Intellij IDE began to appear on Habré - one , and here another .
I will continue this glorious trend and try to describe those places of Intellij OpenAPI that have not yet been touched; and an example would be a plug-in with funny comics.
The extension, in fact, does just one simple thing - it displays in the socket panel fresh pictures from my favorite Geek & Poke , periodically downloading them from the site and caching to disk. Sources, by the way, on GitHub'e .
For those who are especially vigilant - comics are under the good license of CC BY-SA 3.0 , so everything is legal :)
Since the very creation of the project, writing
IntelliJ IDEA (and other IDEs) are able to connect to the network through a proxy, the settings are described in detail in the documentation .
But to make your extension use the global IDE settings, it's worth looking in the direction of the class
The easiest way is to use the method
For example, in the code for some,
At the JetBrains forum, someone swears that this method does not help - but, in my opinion, they are in vain.
I needed to start a separate thread, which would periodically check the main page for updates.
This will help
Add to
And in it we define the method
For localization it’s convenient to use this snippet:
Here it is worth paying attention to several points.
The first is stored
By the way, I advise you to look at the class
The second is an annotation
Third, annotations
For some events, I want to show a beautiful notification. Such, for example:
Here it is worth looking towards the class
The notification group is displayed in the IDE settings, for more details, see the documentation .
How to make the settings page for the plug-in is described in detail in the documentation .
But in short, then, firstly, register in
Secondly, we implement interface methods
My plug-in needed to store pictures on disk - in principle, the same question will arise when writing all sorts of caches that have no place in the project directory or in
Extensions are set to by default
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.
I hope this short FAQ will be useful for beginners delving into the IntelliJ platform. And in the meantime, I proceed to the point for which I began to understand all this - an IDEA plug-in to support one very interesting programming language;)
I will continue this glorious trend and try to describe those places of Intellij OpenAPI that have not yet been touched; and an example would be a plug-in with funny comics.
The extension, in fact, does just one simple thing - it displays in the socket panel fresh pictures from my favorite Geek & Poke , periodically downloading them from the site and caching to disk. Sources, by the way, on GitHub'e .
For those who are especially vigilant - comics are under the good license of CC BY-SA 3.0 , so everything is legal :)
Since the very creation of the project, writing
plugin.xml
and other basic things - as well as links to the relevant documentation - are already described in the above articles, we will not repeat; and I’ll just describe a few questions that I had while developing with their solutions.Proxy support
IntelliJ IDEA (and other IDEs) are able to connect to the network through a proxy, the settings are described in detail in the documentation .
But to make your extension use the global IDE settings, it's worth looking in the direction of the class
com.intellij.util.net.HttpConfigurable
. Its public fields contain all the necessary information: the flag USE_HTTP_PROXY
, for example, says whether we use proxies at all or not; as well as information about the host, port, and user. The easiest way is to use the method
prepareURL
, calling 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 initializing the plug-in
I needed to start a separate thread, which would periodically check the main page for updates.
This will help
ApplicationComponent
. The types of components and their creation are remarkably described in the documentation, in the Plugin Structure article . Add to
plugin.xml
our component:com.abelsky.idea.geekandpoke.ComicsPlugin com.abelsky.idea.geekandpoke.ComicsPlugin
And in it we define the method
initComponent
: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’s convenient to use this snippet:
package com.abelsky.idea.geekandpoke.messages;
// ...
public class MessageBundle {
private static Reference 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(bundle);
}
return bundle;
}
}
Here it is worth paying attention to several points.
The first is stored
ResourceBundle
in SoftReference . This is a fairly common practice in the IDEA source code - to keep as many objects as possible in non-hard links. By the way, I advise you to look at the class
com.intellij.reference.SoftReference
- it is the developers themselves who use it instead of implementing from java.lang.ref
. The difference is that if you suspect a memory leak, com.intellij.reference.SoftReference
you can quickly convert it into a hard link, and this will help with profiling. The second is an annotation
org.jetbrains.annotations.PropertyKey
. It indicates that the annotated argument of the method can only be a string from the one specified in the parameterresourceBundle
the bundle. Its use adds confidence that the keys in the .properties file and in the code are not out of sync (and even refactoring in IDEA learns a lot, since there is a connection between the key and the bundle). Third, annotations
org.jetbrains.annotations.NonNls
/ org.jetbrains.annotations.Nls
, marking lines that should not (or, conversely, should) be translated. JetBrains documentation for use is here .Notifications
For some events, I want to show a beautiful notification. Such, for example:
Here it is worth looking towards the class
com.intellij.notification.Notifications
. For example, like this: 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 the settings page for the plug-in is described in detail in the documentation .
But in short, then, firstly, register in
plugin.xml
:
Secondly, we implement interface methods
com.intellij.openapi.options.Configurable
. Of these, the most important - createComponent
should return a component on which our settings are displayed.Offline cache
My plug-in needed to store pictures on disk - in principle, the same question will arise when writing all sorts of 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 - use the directory in which the plug-in itself is installed. Extensions are set to by default
%USERPROFILE%/.IdeaIC11/config/plugins/PLUGIN_NAME
; this path, however, can be changed by setting the variable idea.plugins.path
to 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 delving into the IntelliJ platform. And in the meantime, I proceed to the point for which I began to understand all this - an IDEA plug-in to support one very interesting programming language;)