~/vscode-extensions/jetbrains-plugin-entwickeln/

JetBrains-Plugin entwickeln mit dem IntelliJ Platform SDK

Wie Sie ein Plugin für IntelliJ IDEA, PyCharm oder WebStorm mit Gradle und Kotlin aufsetzen, Actions und Services registrieren und im JetBrains Marketplace veröffentlichen.

Ein JetBrains-Plugin entwickeln Sie mit dem IntelliJ Platform SDK: einem Gradle-Projekt in Kotlin oder Java, das über das offizielle IntelliJ-Platform-Gradle-Plugin gebaut wird. Am schnellsten starten Sie mit der offiziellen Plugin-Vorlage von JetBrains auf GitHub. Funktionen melden Sie in der Datei plugin.xml an, testen sie in einer separat gestarteten Test-IDE und veröffentlichen das fertige Paket im JetBrains Marketplace.

Ein Plugin läuft in allen IDEs auf Basis der IntelliJ Platform, also etwa in IntelliJ IDEA, PyCharm, WebStorm, PhpStorm oder Rider, soweit es keine produktspezifischen Funktionen voraussetzt. Anders als VS-Code-Extensions laufen JetBrains-Plugins im selben Prozess wie die IDE. Das gibt tiefen Zugriff, verlangt aber Disziplin beim Threading.

Voraussetzungen

  • IntelliJ IDEA (die kostenlose Variante genügt) als Entwicklungsumgebung.
  • Das Plugin DevKit, ein Hilfs-Plugin für die Plugin-Entwicklung. In neueren IDE-Versionen installieren Sie es bei Bedarf über die Plugin-Einstellungen nach.
  • Ein JDK in der Version, die die Ziel-Plattform verlangt. Die Vorlage gibt sie vor, Gradle lädt sie auf Wunsch selbst.
  • Grundkenntnisse in Kotlin oder Java und ein ungefähres Verständnis von Gradle.

Projekt anlegen

Sie haben zwei Wege. Für ernsthafte Projekte empfiehlt sich die Vorlage, für schnelle Experimente der Assistent.

  1. Vorlage: Erzeugen Sie aus der IntelliJ Platform Plugin Template auf GitHub ein eigenes Repository. Sie bringt Gradle-Konfiguration, Beispielcode, Tests, Changelog-Verwaltung und fertige GitHub-Workflows für Build, Prüfung und Veröffentlichung mit.
  2. Assistent: Alternativ wählen Sie in IntelliJ IDEA unter „Neues Projekt“ den Generator für IDE-Plugins.
  3. Passen Sie in gradle.properties Gruppe, Name und Plugin-ID an. Die ID ist dauerhaft und darf nach der Veröffentlichung nicht mehr wechseln.
  4. Starten Sie den Gradle-Task runIde. Es öffnet sich eine zweite IDE-Instanz mit Ihrem Plugin und einem eigenen, sauberen Konfigurationsordner.

Die wichtigsten Dateien

Datei Aufgabe
build.gradle.kts Build mit dem Gradle-Plugin org.jetbrains.intellij.platform, Zielplattform, Abhängigkeiten
gradle.properties Plugin-ID, Version, unterstützte Build-Nummern, Plattform-Version
src/main/resources/META-INF/plugin.xml Manifest: Name, Anbieter, Abhängigkeiten, Actions, Erweiterungen
src/main/kotlin/... Quellcode
CHANGELOG.md Änderungen je Version, die Vorlage übernimmt sie in die Plugin-Beschreibung

Die Datei plugin.xml ist das Gegenstück zur package.json einer VS-Code-Extension. Ein knappes Beispiel:

<idea-plugin>
  <id>de.example.zeilenzaehler</id>
  <name>Zeilenzähler</name>
  <vendor>Beispiel</vendor>
  <depends>com.intellij.modules.platform</depends>

  <actions>
    <action id="Zeilenzaehler.Zaehlen"
            class="de.example.ZeilenAction"
            text="Zeilen zählen"
            description="Zählt die Zeilen der aktuellen Datei">
      <add-to-group group-id="ToolsMenu" anchor="last"/>
    </action>
  </actions>

  <extensions defaultExtensionNs="com.intellij">
    <notificationGroup id="Zeilenzaehler" displayType="BALLOON"/>
  </extensions>
</idea-plugin>

Die Zeile <depends>com.intellij.modules.platform</depends> erklärt, dass das Plugin nur die gemeinsame Plattform braucht und damit in allen IDEs laufen kann.

Eine erste Action

Actions sind Befehle, die in Menüs, Werkzeugleisten oder über Tastenkürzel erscheinen. Sie erben von AnAction:

class ZeilenAction : AnAction() {

  override fun getActionUpdateThread() = ActionUpdateThread.BGT

  override fun update(e: AnActionEvent) {
    // Nur aktiv, wenn ein Editor geöffnet ist
    e.presentation.isEnabledAndVisible = e.getData(CommonDataKeys.EDITOR) != null
  }

  override fun actionPerformed(e: AnActionEvent) {
    val editor = e.getData(CommonDataKeys.EDITOR) ?: return
    val anzahl = editor.document.lineCount
    NotificationGroupManager.getInstance()
      .getNotificationGroup("Zeilenzaehler")
      .createNotification("$anzahl Zeilen", NotificationType.INFORMATION)
      .notify(e.project)
  }
}

update() wird sehr oft aufgerufen, etwa bei jedem Öffnen eines Menüs. Halten Sie die Methode deshalb schnell und ohne Datei- oder Netzwerkzugriffe. Über getActionUpdateThread() geben Sie an, ob update() im Hintergrund (BGT) oder im UI-Thread (EDT) läuft. Der Hintergrund ist für Zugriffe auf Projektdaten in der Regel die bessere Wahl.

Services, Erweiterungen und Listener

Neben Actions kennt die Plattform drei weitere Bausteine:

  • Services halten Zustand und Logik. Ein Service kann für die ganze Anwendung oder für ein einzelnes Projekt gelten. Mit der Annotation @Service und dem Abruf über project.service<MeinService>() erzeugt die Plattform ihn bei Bedarf.
  • Erweiterungen (Extensions) docken an Extension Points der Plattform an: Inspektionen, Code-Vervollständigung, Werkzeugfenster, Einstellungsseiten und vieles mehr. Sie werden in plugin.xml unter <extensions> angemeldet.
  • Listener reagieren auf Ereignisse wie geöffnete Projekte oder geänderte Dateien und werden ebenfalls deklarativ registriert.

Deklarative Registrierung hat einen Vorteil: Die IDE lädt Klassen erst, wenn sie gebraucht werden. Das hält den Start schnell. Vermeiden Sie deshalb Arbeit in Konstruktoren und statischen Initialisierern.

Threading: die wichtigste Regel

Die IntelliJ Platform trennt strikt zwischen dem UI-Thread (Event Dispatch Thread, EDT) und Hintergrund-Threads. Zugriffe auf das Datenmodell, etwa Dokumente oder den Syntaxbaum (PSI), brauchen eine Lese- oder Schreibaktion.

Aufgabe Wo Wie
Oberfläche aktualisieren EDT kurze Operationen, keine Wartezeiten
Code analysieren, Dateien lesen Hintergrund in einer Leseaktion (readAction)
Dokument oder Code ändern EDT in einer Schreibaktion, für Undo über WriteCommandAction
Netzwerkzugriffe, lange Berechnungen Hintergrund Coroutine oder Hintergrund-Task mit Fortschrittsanzeige

Wer diese Regeln verletzt, bekommt in der Test-IDE Fehlermeldungen oder spürbare Verzögerungen. Die Dokumentation des SDK beschreibt das Modell ausführlich und empfiehlt für neuen Code Kotlin-Coroutines.

Einstellungen dauerhaft speichern

Einstellungen und Zustand speichert ein Plugin über die Persistenzschicht der Plattform. Ein Service implementiert dazu PersistentStateComponent und wird mit der Annotation @State versehen, die den Dateinamen festlegt. Die Plattform schreibt den Zustand dann automatisch als XML in den Konfigurationsordner der IDE oder, bei projektbezogenen Einstellungen, in den Projektordner. Eine Einstellungsseite in den IDE-Einstellungen bauen Sie mit einer Erweiterung vom Typ applicationConfigurable oder projectConfigurable, für die Oberfläche bietet die Plattform eine Kotlin-UI-DSL an.

Für Passwörter und Zugangstoken ist diese Persistenz nicht gedacht. Dafür gibt es eine eigene Schnittstelle, die Geheimnisse im Schlüsselbund des Betriebssystems ablegt. Speichern Sie Token nie im Klartext in den XML-Dateien, denn diese landen bei projektbezogenen Einstellungen schnell in einem Repository.

Sprachunterstützung: eigenes Plugin oder LSP?

Wer Unterstützung für eine eigene Sprache bauen will, hat zwei Wege. Der klassische Weg nutzt die Sprach-APIs der Plattform mit eigenem Lexer, Parser und PSI. Das liefert die tiefste Integration, ist aber aufwendig. Neuere Versionen der Plattform können zusätzlich einen vorhandenen Language Server anbinden, allerdings nicht in jeder IDE-Edition. Was ein Language Server leistet und warum er Editoren verbindet, erklärt der Beitrag Language Server Protocol einfach erklärt. Wer denselben Server schon für VS Code geschrieben hat, spart viel Arbeit.

Testen und Kompatibilität prüfen

  • runIde startet die Test-IDE. Mit dem Debugger der Entwicklungs-IDE setzen Sie Haltepunkte in Ihrem Plugin.
  • Tests auf Basis der Testklassen der Plattform laufen ohne sichtbare Oberfläche. Die Vorlage enthält Beispiele.
  • verifyPlugin prüft das Paket mit dem Plugin Verifier gegen die gewünschten IDE-Versionen und meldet die Nutzung entfernter oder interner APIs.

Häufige Probleme

Symptom Ursache Lösung
Action erscheint nicht im Menü falsche group-id oder update() blendet sie aus Gruppe prüfen, Bedingungen in update() testen
Warnung zu Lesezugriff außerhalb einer Leseaktion PSI-Zugriff im falschen Kontext Zugriff in readAction kapseln
Plugin im Marketplace nicht für alle IDEs sichtbar produktspezifische Abhängigkeit deklariert nur com.intellij.modules.platform verwenden, wo möglich
Verifier meldet interne API Nutzung von Klassen mit @ApiStatus.Internal öffentliche Alternative suchen
IDE startet spürbar langsamer Arbeit in Konstruktoren oder Startaktivitäten Arbeit verzögern, Hintergrund nutzen

Die unterstützten Versionen legen Sie über Build-Nummern fest (sinceBuild und optional untilBuild). JetBrains veröffentlicht mehrere große Versionen pro Jahr. Planen Sie ein, Ihr Plugin regelmäßig gegen neue Versionen zu prüfen.

Veröffentlichen im JetBrains Marketplace

  1. Konto anlegen: Melden Sie sich mit einem JetBrains-Konto im Marketplace an und legen Sie bei Bedarf eine Organisation als Anbieter an.
  2. Paket bauen: Der Task buildPlugin erzeugt ein ZIP im Ordner build/distributions.
  3. Signieren: JetBrains empfiehlt, Plugins mit einem eigenen Zertifikat zu signieren. Die Vorlage bringt einen Task dafür mit, Schlüssel gehören in Umgebungsvariablen oder Secrets der CI.
  4. Erste Version hochladen: Laden Sie das ZIP im Marketplace hoch, ergänzen Sie Beschreibung, Lizenz und Kategorie. Neue Plugins prüft JetBrains manuell.
  5. Updates automatisieren: Spätere Versionen lädt der Task publishPlugin mit einem persönlichen Token hoch, etwa aus einem GitHub-Workflow.

Der Marketplace unterstützt auch kostenpflichtige Plugins mit Lizenzverwaltung durch JetBrains. Allgemeines zu Stores, Versionierung und Pflege finden Sie unter Add-ons veröffentlichen und pflegen. Wer zum Vergleich die Gegenseite kennenlernen möchte, liest VS-Code-Extension erstellen mit Yeoman: Dort laufen Erweiterungen in einem eigenen Prozess und kommunizieren nur über die Extension API mit dem Editor.

faq

Fragen und Antworten

Kotlin oder Java für JetBrains-Plugins?
Beides funktioniert, die Plattform selbst ist in Java und Kotlin geschrieben. JetBrains verwendet in Vorlage und Dokumentation überwiegend Kotlin, und viele neuere Beispiele setzen Kotlin voraus. Für neue Projekte ist Kotlin deshalb die naheliegende Wahl, vorhandenes Java-Wissen lässt sich aber vollständig nutzen.
Läuft ein Plugin in allen JetBrains-IDEs?
Nur, wenn es ausschließlich Funktionen der gemeinsamen Plattform nutzt. Sobald Sie etwa die Java-Unterstützung verwenden, müssen Sie die Abhängigkeit in plugin.xml deklarieren, und das Plugin erscheint nur in IDEs, die diese Funktion mitbringen. Über die Abhängigkeitsangaben steuern Sie, für welche Produkte das Plugin angeboten wird.
Warum friert die IDE ein, wenn mein Plugin arbeitet?
Meist läuft eine lange Operation im UI-Thread. Datei- oder Netzwerkzugriffe und Analysen gehören in einen Hintergrund-Task, etwa über Kotlin-Coroutines oder Task.Backgroundable. Zugriffe auf das Datenmodell der IDE erfordern zusätzlich eine Lese- oder Schreibaktion.
Kann ich ein Plugin auch ohne Marketplace verteilen?
Ja. Das gebaute ZIP lässt sich in den Plugin-Einstellungen der IDE von der Festplatte installieren. Unternehmen können außerdem ein eigenes Plugin-Repository betreiben und dessen Adresse in den IDEs hinterlegen.