Optional dependency - JetBrains Markdown plugin

Answered

Newbie issue that I've been struggling with...

What is the proper way to declare an optional dependency on the JetBrains Markdown plugin in my plugin?

I want to be able to use the Markdown split editor (i.e. text editor + html preview pane) in a dialog in my plugin. I know I need to specify a dependency for this in my plugin, so I added the following to my plugin's build.gradle:

intellij {
downloadSources false
plugins 'markdown'
}

Adding this snippet in my build.gradle added the JetBrains Markdown plugin class library to the External Libraries of my project, giving me access to the Java classes used in the Markdown plugin from my plugin's code.

Then I added the following depends tag to my plugin.xml file:

<!-- Markdown specific tasks -->
<depends optional="true" config-file="withMarkdown.xml">org.intellij.plugins.markdown</depends>

I'm not sure what I need to add to the config-file withMarkdown.xml, so I just added an empty extensions block like so:

<idea-plugin>
<extensions defaultExtensionNs="com.intellij.plugins.markdown">

</extensions>
</idea-plugin>

Then, I created a subclass of DialogWrapper and use the following code to create a dialog that wraps a MarkdownSplitEditor inside of it:

@Nullable
@Override
protected JComponent createCenterPanel() {
JPanel container = new JPanel();
container.setLayout(new BoxLayout(container, BoxLayout.PAGE_AXIS));

MarkdownSplitEditorProvider provider = new MarkdownSplitEditorProvider();
Random random = new Random();
int randomInt = random.nextInt(1000);

this.markdownFile = new LightVirtualFile("markdown_" + randomInt, "");
this.markdownFile.setLanguage(MarkdownLanguage.INSTANCE);

this.markdownEditor = provider.createEditor(JetbrainsUtils.getCurrProject(), this.markdownFile);
this.markdownEditor.setState(FileEditorState.INSTANCE);
container.add(this.markdownEditor.getComponent());
return container;
}

This does actually create a split editor inside a dialog when the JetBrains Markdown plugin is installed and enabled:

But if the JetBrains Markdown plugin is not enabled I get a NoClassDef exception whenever I trigger creation of the dialog.

I must be doing something fundamentally wrong here that causes the NoClassDef exception. I understand that when the Markdown plugin is not enabled, the classes I'm using to create the split editor don't exist to my plugin. But I assumed that the optional dependency allowed for a more graceful approach towards handling the case when the Markdown plugin is not enabled.

What am I doing wrong here? Please help...

0
4 comments

Okay I think I figured out my issue. It seems the whole point of the config-file that gets defined as an attribute of the optional depends tag is to use it to define code that should only exist if the optional dependency is enabled. If the optional plugin is not enabled the code constructs defined in the config-file will not exist (i.e. not be loaded by any classloader).

Also, a nifty class that is really useful for what I'm trying to do is the PluginManagerCore class. Using the following:

PluginManagerCore.isDisabled(PluginId.getId(pluginId))

I can check for the existence of the Markdown plugin at runtime and use the condition to possibly execute code that leverages classes from that plugin.

What I ended up doing is adding an action to my with markdown.xml config-file I defined in my optional dependency, writing my Markdown specific code in that action and then leveraging that action only if the Markdown plugin is enabled.

Not sure if this is the best practice way to handle my scenario, but so far it seems to work. Any suggestions on better ways to do this are welcome and much appreciated!

0

You can solve it in two ways:

1. Check if the plugin that you depend on is available using PluginManager as you already did. This method requires a bit of caution because you may use 3rd party classes in your code when it is not enabled.

2. Create a custom extension point in your project (in the main plugin.xml) and define its implementation in the withMarkdown.xml file. Only this implementation will have access to the Markdown classes so your plugin will be completely separated from the classes that may not be available.

1

So with the second method, if I define an interface extension point in my plugin.xml, and then define a class that implements that interface in withMarkdown.xml that uses Markdown classes, wouldn't I still need to check for the existence of the plugin with PluginManagerCore before using the implementation class defined in withMarkdown.xml anywhere in my plugins code? Just trying to wrap my head around this.

Also, when I added plugin = 'markdown' to my build.gradle, the IDE added the latest version of the markdown plugin classes to my project in External Libraries. Is this correct or did I do something wrong here that caused the markdown classes to be added to my plugins external libraries?

0

Such check with PluginManagerCore wouldn't be necessary anymore because your implementation defined in the withMarkdown.xml file will be available in your plugin only in case when Markdown is enabled. The only thing that you'll have to verify is that if you have the EP implementation available.

Regarding the External Libraries question - this is the correct behaviour. You have defined a dependency to the Markdown in the Gradle configuration to make the plugin classes available for your implementation - on the build time. You don't bundle such sources with your plugin at this point. Adding the dependency to the plugin.xml defines that yo depend on such classes which are required in the runtime. Thanks to that, you can build the whole plugin with some Markdown interfaces with the possibility to have it optional.

1

Please sign in to leave a comment.