Hogyan hozzunk létre egy IntelliJ plugint - készítsünk egy egyszerű szótárkeresőt

A legtöbb fejlesztő az IntelliJ platformokat használja, akár IDEA, PHPStorm, WebStorm, Android Studio, PyCharm, és a lista folytatódik. Azonban néha, amikor használjuk, azt tapasztaljuk, hogy egy funkció hiányzik, de fogalmunk sincs arról, hogyan lehet ezt a funkciót valóban hozzáadni, és végül csak nélküle élni.

Ebben a cikkben kitérek arra, hogyan hozhatunk létre egy egyszerű plugint az összes IntelliJ IDE-hez, így amikor egy project.dicfájlt hozzáad , az automatikusan hozzáadja az egyik szótárához. A fájlt csomagokban is megkeresi, így a csomagok egyéni szavakat adhatnak hozzá a szótárhoz. A .dicfájl egy egyszerű szótár, ahol minden sor egy szó a szótárban.

A projekt csak egy példa arra, hogy elkezdhesse saját pluginek fejlesztését. De valójában ez egy olyan funkció is, amiről hiányoltam, hiszen amikor kidolgozok egy egyedi csomagot a saját szavaimmal, utálom, hogy minden alkalommal hozzá kell adnom őket a projekt szintű szótárhoz.

A projekt elkészítése

Az IntelliJ beépülő modulok létrehozásakor opcióként meg kell adnunk Java vagy Kotlin használatával. Java-ban fogom csinálni, mivel a legtöbb felhasználó ismeri ezt. Mivel ez egy Java projekt, az IntelliJ IDEA-t fogjuk használni IDE-ként.

A fejlesztési útmutató szerint a projekt létrehozásának ajánlott módja a Gradle használata. Kezdjük azzal, hogy nyitunk, preferencesés ellenőrizzük, hogy vannak-e telepítve Gradleés Plugin DevKitbeépülő modulok.

A beépülő modulok telepítése és az IDE újraindítása után áttérünk az új projektfolyamatra Gradle. Itt van egy opció, IntelliJ Platform Pluginamelyre szükségünk van.

Ezután menjen végig a projekt létrehozásának többi részén a szokásos módon - ebben a projektben a következő konfigurációt választom.

Felállítása plugin.xml

Most, hogy van egy projektünk, be kell állítanunk a plugin.xmlfájlunkat és build.gradle. A plugin.xmlfájl az IntelliJ által használt fájl, amely meghatározza a pluginnal kapcsolatos összes információt. Ez magában foglalja a nevet, a függőségeket, milyen műveleteket kell hozzáadnia, vagy ha ki kell terjesztenie valamit az IntelliJ-ben. Alapvetően ez a fájl meghatározza a plugin által elvégzendő mindent, és ez a projekt gyökere. A fájlunkban build.gradlemeghatározhatunk néhány értéket plugin.xml, és információkat, például, hogy az IntelliJ melyik verzióján szeretnénk tesztelni a beépülő modulunkat, amikor gradlével építünk.

Kezdjük a plugin.xmlfájlunk definiálásával . A fájlt itt találja src/main/resources/META-INF/plugin.xml. Azt akarjuk, hogy a beépülő modul áll rendelkezésre minden IntelliJ IDE így mi meg dependencies, hogy com.intellij.modules.lang. Jelenleg a fájlunk így néz ki:

 dk.lost_world.Dictionary Dictionary GitHub com.intellij.modules.lang

Ennek azonban jelenleg nincs logikája, és semmit sem regisztrálunk az IntelliJ platformra.

Mivel ez a projekt project.dicfájlokat talál egy projekten belül, és szótárként regisztrálja őket a projektben, regisztrálnunk kell egy projekt szintű összetevőt. Ezt az összetevőt akkor hívják meg, amikor egy projektet nyitnak és zárnak. Hozzunk létre egy osztályt, és valósítsuk meg az ProjectComponentinterfészt. Amikor az osztály neve fölé visszük az egérmutatót, az azt közli velünk, hogy az alkatrész nincs regisztrálva.

Ezután hívhatjuk a műveletet hívottnak, Register Project Componentés regisztrálja nekünk a plugin.xmlfájlban.

Ha megnyitjuk plugin.xmla következő kódot kell hozzáadni. Ha nem adta hozzá a művelet meghívásakor, akkor csak manuálisan adja hozzá.

  dk.lost_world.dictionary.DictionaryProjectComponent 

IntelliJ fájlrendszer

Amikor dolgozik fájlokat IntelliJ, használjuk a V irtual F ile S ystem (VFS). A VFS univerzális API-t biztosít a fájlokkal való beszélgetéshez, anélkül, hogy át kellene gondolnunk, hogy FTP-ből, HTTP-kiszolgálóról vagy csak a helyi lemezről származnak-e.

Mivel a beépülő modul néz ki a fájl neve project.dicez lesz természetesen kell beszélni a V irtual F ile S ystem. A VFS-ben található összes fájl virtuális fájl. Ez kissé félelmetesen hangozhat, de a valóságban ez csak egy fájlrendszer és egy fájl API-ja. Az út gondolni, éppen ez a V irtual F ile S ystem van a fájlrendszer felület és a virtuális fájlok vannak a fájlok.

Helyesírás-ellenőrző beállításai

Mivel az IntelliJ már támogatja a .dicfájlokat és általában a helyesírás-ellenőrzést, csak annyit kell tennünk, hogy regisztráljuk a project.dicfájljainkat a helyesírás-ellenőrző beállításaiban.

A helyesírás-ellenőrző összes beállítását egy úgynevezett osztályba menti com.intellij.spellchecker.settings.SpellCheckerSettings. A példány megszerzéséhez egyszerűen hívja meg a getInstancemetódust (az IntelliJ osztályok többsége kapott egy olyan getInstancemódszert, amely az IntelliJ alját használja ServiceManager).

A beállítási osztály kapott egy úgynevezett metódust, getCustomDictionariesPathsamely az összes utat visszaküldi a felhasználó által telepített szótárakba.

A metódus aláírásának megtekintésekor egy feljegyzést is látunk AvailableSince. Később az ebben a feljegyzésben szereplő értéket használjuk a plugin működéséhez szükséges minimális verzió meghatározásához.

Mivel a metódus visszaad egy listát, egyszerűen meghívhatjuk adda metódust, hogy új utat adjunk hozzá egy szótárhoz.

Bővítményünk futtatása (build.gradle)

Mivel most már tudjuk, hogyan kell szótárat hozzáadni a helyesírás-ellenőrzőhöz, adjunk hozzá egy kis kód példát az DictionaryProjectComponentosztályunkban ehhez.

public class DictionaryProjectComponent implements ProjectComponent { private Project project; public DictionaryProjectComponent(Project project) { this.project = project; } @Override public void projectOpened() { SpellCheckerSettings .getInstance(project) .getCustomDictionariesPaths() .add("./project.dic"); }}

Ez a kód project.dica projekt megnyitásakor regisztrál egy fájlt a projekt gyökérkönyvtárából.

A kis példánk kipróbálására frissítenünk kell a build.gradlefájlunkat. A intellijgradle fájl szakaszában hozzáadjuk, hogy az IntelliJ melyik verziójában szeretnénk használni. Ez a verziószám AvailableSinceaz SpellCheckerSettingsosztály feljegyzéséből származik .

plugins { id 'java' id 'org.jetbrains.intellij' version '0.4.4'}group 'dk.lost_world'version '1.0-SNAPSHOT'sourceCompatibility = 1.8repositories { mavenCentral()}dependencies { testCompile group: 'junit', name: 'junit', version: '4.12'}// See //github.com/JetBrains/gradle-intellij-plugin/intellij { pluginName 'Dictionary' version '181.2784.17' type 'IC' downloadSources true}

Running the runIde command from gradle will start up an instance of IntelliJ of the specific version. After starting up the testing IDE our plugin should have been run. If we open up preferences > Editor > Spelling > Dictionaries we can see under custom dictionaries that the path we specified in our example is now added.

We are now able to test our plugin, so now it is time to build it out correctly so it finds the project.dic files and registers them for us.

In the DictionaryProjectComponent::projectOpened method, we need to first find all files called project.dic and register them and also add a file listener so when new project.dic files are added, they are registered automatically.

Dictionary Class

We will have a class called Dictionary, this class will contain the logic for us to register and remove files from the dictionary. The class will have the following public methods:

void registerAndNotify(Collection files)

void registerAndNotify(VirtualFile file)

void removeAndNotify(VirtualFile file)

void moveAndNotify(VirtualFile oldFile, VirtualFile newFile)

These methods will also create a notification about what happened, so the end user knows what changed with the custom dictionaries. The end file for this will look the following way:

Finding all dictionary files

For finding all the dictionary files in the project called project.dic we use the class FilenameIndex. The file is in the namespace com.intellij.psi.search.FilenameIndex, it has a method getVirtualFilesByName which we can use to find our project.dic files.

FilenameIndex.getVirtualFilesByName( project, "project.dic", false, GlobalSearchScope.allScope(project))

This call will return all Virtual Files which matches the search criteria. We then put the return result into the Dictionary class method registerAndNotify.

@Overridepublic void projectOpened() { Dictionary dictionary = new Dictionary(project); dictionary.registerAndNotify( FilenameIndex.getVirtualFilesByName( project, "project.dic", false, GlobalSearchScope.allScope(project) ) );}

Our code is now able to find project.dic files at start up and register them, if they are not already registered. It will also notify about the newly registered files.

Adding a Virtual File Listener

The next part is for us to listen for changes in virtual files. To do this we need a listener. For this we need the com.intellij.openapi.vfs.VirtualFileListener.

In the docblock for the listener class we can see that to register it we can use VirtualFilemanager#addVirtualFileListener.

Let’s create a class named DictionaryFileListener and implement the methods which we need for our project.

Then we update our projectOpened class to also add the VirtualFileListener.

@Overridepublic void projectOpened() { Dictionary dictionary = new Dictionary(project); dictionary.registerAndNotify( FilenameIndex.getVirtualFilesByName( project, "project.dic", false, GlobalSearchScope.allScope(project) ) ); VirtualFileManager.getInstance().addVirtualFileListener( new DictionaryFileListener(dictionary) );}

Our plugin is now able to find our dictionary files at startup, but also listen for if a dictionary file is added later on. The next thing we need is to add information for our plugin listing.

Adding plugin information

To add information about the plugin, we open the build.gradle file and edit the object patchPluginXml. In here we need to specify which build version is required for the plugin, version of the plugin, description and change notes.

patchPluginXml { sinceBuild intellij.version untilBuild null version project.version pluginDescription """Plugin for having a shared dictionary for all members of your project.

It will automatically find any project.dic files and add themto the list of dictionaries.

It will also search packages for dictionary files and add them to our list of dictionaries. """ changeNotes """

0.2

  • Added support for listening for when a project.dic file is added, moved, deleted, copied.

0.1

  • First edition of the plugin.
"""}

We also update the version property to '0.2'of the gradle project itself. The plugin can now run on all versions since the method for registering custom dictionaries was added.

To test if it generates the desired output, we can run the gradle task patchPluginXml and under build/patchedPluginXmlFiles our generated plugin.xml file will be there.

Since IntelliJ version 2019.1, all plugins supports icons. As this is fairly new a lot of plugins do not have an icon, and your plugin can stand out a lot by having one. The naming convention is pluginIcon.svg as the default icon and pluginIcon_dark.svg for the darcula theme.

The plugin icons should be listed together with the plugin.xml file in the path resources/META-INF.

Building for distribution

The plugin is now ready to be built and shipped. To do this we run the gradle task buildPlugin. Under build/distributions a zip file will appear which you can distribute and install manually in your IDE. Add this zip file as a release under your github repo, so users have the option to download it manually from you repo.

Publishing a plugin

To publish our plugin so it can be downloaded directly from IntelliJ’s plugin repository, we need to login on our JetBrains account on the Plugin Repository website. When in here, a dropdown from your profile name shows an option to upload a plugin.

Input all the information in the dialog (you have to add a license, but that is pretty straightforward with Github). Here we add the distribution zip file.

When you submit the form, you can now see your plugin in the plugin repository. However other users do not have access to it before IntelliJ has approved it. Approving your plugin normally takes 2–3 days.

Updating your plugin via Gradle

After the plugin has been created, we can update it programmatically. To do this the best practice is to create a token. Open up jetbrains hub and go to the authentification tab. From here press New token... and add the scope Plugin Repository.

When pressing create you get a token. Create a file called gradle.properties and add the token under the key intellijPublishToken (remember to git ignore this file).

In our build.gradle file, we simply add the following:

publishPlugin { token intellijPublishToken}

And we can now run the gradle task publishPlugin for publishing our new version. All versions numbers have to be unique or else it will fail updating. When an update is created, you have to wait 2–3 days again for them to approve the update.

After waiting some days our plugin has now been approved and can now be found in the plugin marketplace by searching for dictionary!

Conclusion

I hope this article has given you more courage to start developing your own plugins. One of the biggest problems I had while developing it was to find out which classes to use. IntelliJ has an extensive guide which I would recommend that you read from start to end, however a lot of classes are not mentioned in there. In cases where you get stuck, they have a Gitter chat which is really helpful and there are people from IntelliJ on there to help also.

The source code for this project can be found on Github and the plugin we created is in the JetBrains marketplace.