Skip to main content
  1. Posts/

Building an IntelliJ Plugin for YAML and JSON Conversion

·1732 words·9 mins

Creating plugins for IntelliJ is an exciting journey. From setting up your environment to publishing your work for the broader community. In this guide, I’ll share my experience developing the JSON Plus YAML Plugin, which converts JSON to YAML and vice versa.

🏁 How to Start? #

The Developing a plugin page is an excellent starting point. It offers a comprehensive overview of IntelliJ plugin development using either IntelliJ IDEA Community Edition or Ultimate .

When it comes to hands-on instructions, I recommend starting with the IntelliJ platform plugin template . Using their template really simplifies the initial setup.

💡Just remember to keep your template updated.💡

For my plugin, I chose Java over Kotlin, which required minor tweaks to the template. Simply rename the package from kotlin to java, and you’re good to go.

🏗️ Breaking It Down #

I followed six steps to create this plugin and achieve my desired results. Here, I list those steps and later I will address each in detail.

  1. Setting Up the Plugin Project
  2. Understanding IntelliJ Plugin Architecture
  3. Configuring plugin.xml
  4. Implementing the Conversion Logic
  5. Testing the Plugin
  6. Packaging and Distribution

1. Setting Up the Plugin Project #

Before starting, ensure IntelliJ IDEA is installed on your machine. It’s recommended to create a repository using this template to establish a solid foundation. For more on creating a repository from a template, refer to GitHub’s guide .

2. Understanding IntelliJ Plugin Architecture #

Now that you have in hand a solid foundation for your plugin, let’s understand a bit more about how we can leverage IntelliJ features to improve our daily productivity. When creating JetBrains ecosystem plugins, remember that a plugin can combine actions, services, and extensions:

  • Actions: User-triggered operations, such as the JSON to YAML conversion.
  • Extensions: Enable plugins to enhance IntelliJ’s existing components.
  • Services: Handle complex or background operations.

In my case, I focused on actions, specifically adding them in the file tab, project file and context menus for conversion, because I was looking to enhance my productivity by adding a new feature to the IntelliJ platform. So, according to the idea you have in mind, you should pick up the correct architecture, or even a combination of the mentioned options.

3. Configuring plugin.xml #

The plugin.xml file is the heart of any IntelliJ plugin. It defines metadata, actions, and extensions. For example, in the file, you will declare if you will be using an action. This file can be located at src/main/resources/META-INF/plugin.xml and, in my case, I defined my actions to trigger the conversion logic. For instance:

<action id="YamlToJsonActionFromEditor"  
   class="com.example.actions.YamlToJsonAction"  
   text="Convert YAML to JSON" description="Convert YAML content to JSON format"\>  
   <add-to-group group-id="EditorPopupMenu" anchor="last"\>  
</action\>

Please, note that the value com.example.actions.YamlToJsonAction provided for the key class corresponds to the actual path where you will be adding your action class. The add-to-group group-id is the IntelliJ predefined Id which will describe where the action will be taking place. In my case, EditorPopupMenu means that I am triggering the action in the actual file content.

4. Implementing the Conversion Logic #

In order to implement the conversion logic while getting information from the IntelliJ interface, I performed the following steps:

  1. Create a New Package: Begin by creating a new package in your src/main/java directory, such as com.example.actions. This will help you organize your classes logically.
  2. Define the Action Classes: Create a class that extends AnAction. Extending this class enables the plugin to respond to user-triggered actions. Inside your action class, override the actionPerformed method. This is where I performed the text conversion between JSON and YAML formats.
  3. Add Dependencies for YAML and JSON Processing: Update your build.gradle.kts file to include the necessary libraries for conversion. I used Jackson for JSON serialization/deserialization and SnakeYAML for working with YAML.
  4. Implement the Conversion Logic:
    • Use SnakeYAML and Jackson to convert between YAML and JSON. Start by retrieving the text from the active document in IntelliJ.
    • Refer to the working with text tutorial to understand how to access the Editor (the currently open document).
  5. Integrate Undo/Redo Functionality: Use the runWriteCommandAction method to modify the document. This ensures that the changes are tracked, allowing users to undo or redo their actions easily.

At the end, you should end up with a method like the following one.

@Override
public void actionPerformed(@NotNull AnActionEvent event) {

	Project project = event.getProject();

	try {
    	Editor editor = FileEditorManager.getInstance(Objects.requireNonNull(project)).getSelectedTextEditor();

    	if (editor == null) throw new IllegalStateException("No active editor found");

    	Document document = editor.getDocument();

    	String jsonInput = document.getText();

    	if (jsonInput.isEmpty()) throw new IllegalArgumentException("Document is empty");

    	String yamlOutput = JsonYamlConverter.convertToYaml(jsonInput);

    	WriteCommandAction.runWriteCommandAction(project, () \-\> {
        	document.setText(yamlOutput);
        	changeFileExtensionWithUndoRedo(document, "yaml");
    	});
	} catch (Exception e) {
    	Messages.showMessageDialog(project, e.getMessage(), "Error", Messages.getErrorIcon());
	}
}
  1. Checking the Plugin Locally: Run your plugin in the IntelliJ sandbox environment by selecting Run Plugin. Open a file with JSON content, trigger the conversion action, and verify that the content changes to YAML. Check for error handling by providing invalid input, and ensure appropriate error dialogs are displayed.

5. Testing the Plugin #

This plugin includes only unit tests, since it only introduces a new action in the IDE that triggers the conversion itself or dialog boxes upon errors. UI tests were not included, as the IntelliJ community notes the IntelliJ test framework does not support testing UI components, such as dialogs. So, I put my efforts into writing unit tests to check the logic behind the UI to deliver a more reliable functionality.

6. Packaging and Distribution #

At the end of the development, I documented the plugin with a README.md file, which is crucial to ensure clarity for users. In the README, I tried to outline the plugin’s purpose, provide step-by-step installation instructions, explain how to use it, and include examples of its functionality.

When it comes to publishing, you can do it manually or automate the process using Gradle. In my case, I first did it manually and automated it later. But if you feel confident enough, you can try to automate it from the very beginning. The IntelliJ Plugin Template offers a detailed guide on setting up environment variables needed for automation. Here’s an overview of the necessary environment variables:

  • PRIVATE_KEY: The certificate private key.
  • PRIVATE_KEY_PASSWORD: Password used to encrypt the certificate file.
  • CERTIFICATE_CHAIN: The certificate chain.
  • PUBLISH_TOKEN: The publishing token generated in the JetBrains Marketplace profile dashboard.

To avoid exposing sensitive data, I defined these variables as environment variables. For example, you can configure them in the ⚙️ Settings > Secrets and variables > Actions section of your GitHub repository. More details on how to get these variables can be found in the plugin signing page and publishing plugin With Gradle page . Automating this process simplifies future deployments and ensures consistency.

📌 Lessons Learned #

When I first created my plugin, I used IntelliJ Platform Gradle Plugin 1.0. However, when I decided to support the IntelliJ 2024 edition, I discovered that my plugin was no longer compatible. The easiest solution would have been to update my code by syncing with the latest changes in the template source repository. Unfortunately, I didn’t realize this at the time and instead tried to resolve each issue as it arose.

I researched how other projects on GitHub were adapting to the new dependency version, and, initially, everything seemed to work well. However, I encountered a problem in my GitHub Actions build process. A specific Gradle task failed because its name had changed in the updated dependency version. That’s when I found this documentation , which clarified the changes.

The documentation led me to a pull request in the IntelliJ Platform Plugin Template repository, which updated dependencies and refined configurations for compatibility. In hindsight, regularly updating my template to reflect upstream changes would have saved me a significant amount of time and effort. This experience reinforced the importance of keeping our code up to date with the template to avoid unnecessary challenges.

🔮 Making Your Plugin Future-Proof #

The supported IntelliJ version range is set using two fields: pluginSinceBuild and pluginUntilBuild, which can be located under the gradle.properties file, or directly under the build.gradle.kts file. Since my plugin doesn’t rely on specific IntelliJ features, I decided to make it available for any future version of IntelliJ IDE.

According to a response in the IntelliJ support forum , it’s not necessary to specify versions far in advance (e.g., untilBuild = '251'). Instead, we can set the untilBuild field to an empty or null value to avoid restricting compatibility with future versions. Following this guidance, I removed the pluginUntilBuild field from gradle.properties and set the untilBuild variable in the build.gradle.kts file to untilBuild = provider { null }. This approach, which is also suggested in the task documentation , will ensure I don’t need to release a new plugin version every time a new version of IntelliJ IDE is released.

🥇 Conclusion #

Developing my first IntelliJ plugin was both exciting and challenging. The process involved overcoming many technical hurdles, from setting up the development environment to ensuring my plugin worked seamlessly across multiple IntelliJ versions. Along the way, I learned valuable lessons about how to handle the intricacies of plugin architecture, the dependencies, and the plugin configuration. Although the process had challenges, each challenge provided an opportunity to grow and deepen my understanding of the IntelliJ platform.

If you’re thinking about building your own plugin, don’t hesitate! The experience is incredibly rewarding. While the learning curve may seem steep at first, the resources available, such as the IntelliJ Plugin Template and the vast community of developers, make it possible for anyone to succeed. Whether you’re interested in solving a specific problem or just want to contribute to the broader developer community, creating a plugin can be a fulfilling and impactful project.

References #

Baeldung. (2024, May 15). How to process YAML with Jackson. Retrieved from https://www.baeldung.com/jackson-yaml

Hillmann, D. (2024). YAML Plus JSON (Version 1.12.2) [Software]. Visual Studio Marketplace. Retrieved from https://marketplace.visualstudio.com/items?itemName=hilleer.yaml-plus-json

JetBrains. (2024, July 30). Basics of working with the editor. IntelliJ Platform SDK Documentation. Retrieved from https://plugins.jetbrains.com/docs/intellij/editor-basics.html

JetBrains. (2024, October 2). IntelliJ platform Gradle plugin (2.x). IntelliJ Platform SDK Documentation. Retrieved from https://plugins.jetbrains.com/docs/intellij/tools-intellij-platform-gradle-plugin.html

JetBrains. (2024, October 31). Creating a plugin Gradle project. IntelliJ Platform SDK Documentation. Retrieved from https://plugins.jetbrains.com/docs/intellij/creating-plugin-project.html

JetBrains. (2024, October 31). Developing a plugin. IntelliJ Platform SDK Documentation. Retrieved from https://plugins.jetbrains.com/docs/intellij/developing-plugins.html#gradle-plugin

OpenAI. (2024). ChatGPT (Version GPT-4) [Large language model]. Retrieved from https://chat.openai.com

TeamCode. (2023, June 1). How to avoid unchecked casts in Java programs. Medium. Retrieved from https://medium.com/@teamcode20233/how-to-avoid-unchecked-casts-in-java-programs-1f87bf33e9c4

Lilian Cavalet
Author
Lilian Cavalet
Lilian is a Brazilian Software Engineer with a Mathematics background. She has experience helping to build, test, document, maintain, and deploy, to the cloud, more than 10 micro-services, with multiple programming languages, data storage systems, architectures, and communication patterns. She has experience with different-sized companies and agile methodologies (Scrum and Kanban). Committed to continuous learning, Lilian embraces the motto “always hungry for food and knowledge” and advocates for Software Craftsmanship.