Hvordan lage et IntelliJ-plugin - la oss bygge en enkel ordbokfinner

De fleste av oss utviklere bruker IntelliJ-plattformer, enten IDEA, PHPStorm, WebStorm, Android Studio, PyCharm, og listen fortsetter og fortsetter. Noen ganger når vi bruker den, oppdager vi imidlertid at en funksjon mangler, men vi aner ikke hvordan vi faktisk skal legge til den funksjonen og til slutt bare leve uten den.

I denne artikkelen vil jeg dekke hvordan vi kan lage et enkelt plugin for alle IntelliJ IDE- project.dicene, så når du legger til en fil, vil den automatisk legge den til som en av ordbøkene dine. Den vil også søke etter filen i pakker, slik at pakker kan legge til ord i ordboken. En .dicfil er en enkel ordbok der hver linje er et ord i ordboken.

Prosjektet er bare et eksempel for å komme i gang med å utvikle dine egne plugins. Men det er faktisk også en funksjon jeg har savnet, som når jeg utvikler en tilpasset pakke med egne ord i, hater jeg at jeg må legge dem til hver gang i ordboken for prosjektnivå.

Opprette prosjektet

Når du oppretter plugins for IntelliJ, må vi velge å gjøre det i Java eller Kotlin. Jeg vil gjøre det på Java da de fleste brukere er kjent med det. Siden dette er et Java-prosjekt, vil vi bruke IntelliJ IDEA som IDE.

I følge utviklingsguiden er den anbefalte måten å lage et prosjekt på, ved å bruke Gradle. Vi starter med å åpne opp preferencesog sjekke om Gradleog Plugin DevKitplugins er installert.

Etter å ha installert programtilleggene og startet IDE på nytt, går vi til de nye prosjektene og under Gradle. Her inne er det nå et alternativ IntelliJ Platform Pluginsom heter det vi trenger.

Gå deretter gjennom resten av prosjektopprettingsflyten som normalt - i dette prosjektet velger jeg følgende konfigurasjon.

Setter opp plugin.xml

Nå som vi har et prosjekt, må vi sette opp plugin.xmlfilen vår og build.gradle. Den plugin.xmlfil er en fil som brukes av IntelliJ som definerer all informasjon om tillegget. Dette inkluderer navnet, avhengigheter, hvilke handlinger det skal legge til, eller om det skal utvide noe i IntelliJ. I utgangspunktet definerer denne filen alt pluginet ditt skal gjøre, og er roten til prosjektet ditt. I build.gradlefilen vår kan vi definere noen av verdiene fra plugin.xml, og informasjon som hvilken versjon av IntelliJ vi vil teste pluginet vårt når vi bygger med gradle.

La oss starte med å definere plugin.xmlfilen vår . Du finner filen i src/main/resources/META-INF/plugin.xml. Vi vil at pluginet vårt skal være tilgjengelig på alle IntelliJ IDE-er, så vi setter vårt dependenciestil com.intellij.modules.lang. Akkurat nå ser filen vår slik ut:

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

Men akkurat nå har dette ingen logikk, og vi registrerer ikke noe på IntelliJ-plattformen.

Siden dette prosjektet vil finne project.dicfiler i et prosjekt og registrere dem som ordbøker i det prosjektet, må vi registrere en komponent på prosjektnivå. Denne komponenten kalles når et prosjekt åpnes og lukkes. La oss lage en klasse og implementere ProjectComponentgrensesnittet. Når vi holder markøren over klassenavnet, forteller den oss at komponenten ikke er registrert.

Vi kan da ringe handlingen som blir kalt, Register Project Componentog den vil registrere den for oss i plugin.xmlfilen.

Hvis vi åpner, plugin.xmlskal følgende kode legges til. Hvis det ikke ble lagt til når du ringer til handlingen, er det bare å legge til det manuelt.

  dk.lost_world.dictionary.DictionaryProjectComponent 

IntelliJ filsystem

Når vi jobber med filer i IntelliJ, bruker vi en V irtual F ile S ystem (VFS). VFS gir oss en universell API for å snakke med filer, uten at vi trenger å tenke på om de er fra FTP, en HTTP-server eller bare på den lokale disken.

Ettersom pluginet vårt ser etter filer som kalles, må project.dicdet selvfølgelig snakke med V irtual F ile S ystem. Alle filene i VFS er virtuelle filer. Dette kan høres litt skremmende ut, men i virkeligheten er det bare et API for et filsystem og for en fil. Måten å tenke på det er bare at V irtual F ile S ystem er filsystemgrensesnittet ditt og Virtual Files er filene dine.

Stavekontrollinnstillinger

Ettersom IntelliJ allerede har støtte for .dicfiler og stavekontroll generelt, er det eneste vi trenger å gjøre å registrere project.dicfilene våre i stavekontrollinnstillingene.

Alle innstillingene for stavekontrollen lagres i en klasse som heter com.intellij.spellchecker.settings.SpellCheckerSettings. For å få en forekomst av det, kan du bare ringe getInstancemetoden (de fleste IntelliJ-klassene fikk en getInstancemetode som bruker IntelliJs ServiceManagerunder).

Innstillingsklassen fikk en metode som heter getCustomDictionariesPathssom returnerer alle banene til ordbøker som er installert av brukeren.

Når vi ser på metodesignaturen, ser vi også en kommentar kalt AvailableSince. Vi vil senere bruke verdien i denne kommentaren til å spesifisere den minste nødvendige versjonen for at pluginet skal fungere.

Når metoden returnerer en liste, kan vi ganske enkelt ringe addtil metoden for å legge til i en ny bane til en ordbok.

Kjører pluginet vårt (build.gradle)

Som vi nå vet hvordan vi legger til en ordbok i stavekontrollen, la oss legge til et lite kodeeksempel i DictionaryProjectComponentklassen for å gjøre dette.

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

Denne koden vil registrere en project.dicfil fra roten til prosjektet vårt når prosjektet åpnes.

For å teste ut vårt lille eksempel, må vi oppdatere build.gradlefilen vår . I intellijdelen av gradefilen legger vi til i hvilken versjon av IntelliJ vi vil bruke. Dette versjonsnummeret er det fra AvailableSincemerknaden på SpellCheckerSettingsklassen.

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}

Å kjøre runIdekommandoen fra gradle starter en forekomst av IntelliJ av den spesifikke versjonen. Etter at IDE-testen ble startet, burde pluginet ha blitt kjørt. Hvis vi åpner opp preferences > Editor > Spelling > Dicfagbøker, kan vi se under egendefinerte ordbøker at banen vi spesifiserte i eksemplet vårt nå er lagt til.

Vi kan nå teste pluginet vårt, så nå er det på tide å bygge det ut riktig slik at det finner project.dicfilene og registrerer dem for oss.

I DictionaryProjectComponent::projectOpenedmetoden må vi først finne alle kalt filer project.dicog registrere dem, og også legge til en fillytter, så når nye project.dicfiler legges til, blir de registrert automatisk.

Ordboksklasse

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!

Konklusjon

Jeg håper denne artikkelen har gitt deg mer mot til å begynne å utvikle dine egne plugins. Et av de største problemene jeg hadde mens jeg utviklet det, var å finne ut hvilke klasser jeg skulle bruke. IntelliJ har en omfattende guide som jeg vil anbefale deg å lese fra start til slutt, men mange klasser er ikke nevnt der inne. I tilfeller der du setter deg fast, har de en Gitter-chat som er veldig nyttig, og det er også folk fra IntelliJ der for å hjelpe.

Kildekoden for dette prosjektet finner du på Github, og pluginet vi opprettet er på JetBrains-markedet.