{"id":107047,"date":"2021-01-20T10:12:06","date_gmt":"2021-01-20T09:12:06","guid":{"rendered":"https:\/\/blog.jetbrains.com\/?post_type=platform&#038;p=107047"},"modified":"2022-08-09T17:15:57","modified_gmt":"2022-08-09T16:15:57","slug":"revamping-plugins-ignore","status":"publish","type":"platform","link":"https:\/\/blog.jetbrains.com\/pt-br\/platform\/2021\/01\/revamping-plugins-ignore","title":{"rendered":"Revamping Plugins #1 &#8211; .ignore"},"content":{"rendered":"<p>The IntelliJ Platform SDK changes dynamically, and each major release of JetBrains IDEs fixes bugs, adds new features, or introduces breaking changes in the SDK. Because of that, it\u2019s essential to keep track of the <a href=\"https:\/\/plugins.jetbrains.com\/docs\/intellij\/api-changes-list.html#known-breaking-changes\" target=\"_blank\" rel=\"noopener\">Known Breaking Changes<\/a> list in the <a href=\"https:\/\/plugins.jetbrains.com\/docs\/intellij\" target=\"_blank\" rel=\"noopener\">IntelliJ Platform SDK Documentation<\/a>. The plugins that we\u2019re working on can sometimes become outdated, and to catch up with all the changes required to get them up-to-date and compatible with the latest IDE releases is difficult.<\/p>\n<p>The <em>Revamping Plugins<\/em> series is about sharing the experience of updating outdated plugins to align with the latest IntelliJ Platform SDK guidelines. We hope this series will help you to understand the process of doing this and the tools JetBrains provide to make it easier.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2021\/01\/DSGN-10375_Revamping-Plugins_blog_1920x1080.png\" alt=\"\" \/><\/p>\n<h2>About .ignore<\/h2>\n<p>The main idea of the .ignore plugin is to provide an easy way for creating .gitignore files using predefined templates provided by the official GitHub repository \u2013 <a href=\"https:\/\/github.com\/github\/gitignore\" target=\"_blank\" rel=\"noopener\">github\/gitignore<\/a>. The first version of it was released in June 2014 and initially supported only .gitignore files. Further releases introduced new features, including syntax highlighting, custom user templates, and other ignore-like file support.<\/p>\n<p>IntelliJ IDEA 2019.2 introduced native support for the dot-ignore files handling of Git (<em>.gitignore<\/em>), Mercurial (<em>.hgignore<\/em>), and Perforce (<em>.p4ignore<\/em>). As a result, some parts of the implementation were disabled, and the plugin was put into maintenance.<\/p>\n<p>Why did we decide to revamp the .ignore plugin? We were working on the 2020.3 versions of our IDEs which were due to be released in late 2020, and the plugin was missing support for them. That is why we applied the changes described below to make the adjustment process for the latest IDE versions easier. As a bonus, removing dead code and introducing GitHub Actions as CI\/CD has improved the plugin\u2019s stability and quality.<\/p>\n<h2>The revamping process<\/h2>\n<h3>README file<\/h3>\n<p>The .ignore plugin repository is located in the JetBrains GitHub account, <a href=\"https:\/\/github.com\/JetBrains\/idea-gitignore\" target=\"_blank\" rel=\"noopener\">JetBrains\/idea-gitignore<\/a>. We can see that the README file is descriptive and provides a brief introduction to the plugin, installation guidance, and a few details about contributing to the project.<\/p>\n<p>We have introduced a few enhancements:<\/p>\n<ul>\n<li>The text was checked over using Grammarly<\/li>\n<li>Added a couple of badges such as build status<\/li>\n<li>Performed a general cleanup<\/li>\n<li>Added new demo recordings<\/li>\n<\/ul>\n<h3>Build configuration<\/h3>\n<p>The .ignore plugin is built on the Gradle build system which is written in Groovy, and uses four plugins:<\/p>\n<ul>\n<li><code>jacoco<\/code> \u2013 provides code coverage metrics for Java code via integration with JaCoCo.<\/li>\n<li><code>org.jetbrains.intellij, v0.4.21<\/code> \u2013 supports building plugins for IntelliJ-based IDEs.<\/li>\n<li><code>de.undercouch.download, v4.0.0<\/code> \u2013 adds a download task to Gradle that displays progress information.<\/li>\n<li><code>com.github.kt3k.coveralls, v2.8.4<\/code> \u2013 sends coverage data to coveralls.io.<\/li>\n<\/ul>\n<p>The plugin provides custom BNF and Lexer definitions for describing the language of .gitignore and other ignore-like file types to allow for syntax highlighting, navigation, and refactoring. In the <code>resources\/bnf directory<\/code>, we can find <code>Ignore.bnf<\/code> used by the <a href=\"https:\/\/github.com\/JetBrains\/Grammar-Kit\" target=\"_blank\" rel=\"noopener\">Grammar-Kit<\/a> to generate the parser. The JFlex file is located in <code>src\/mobi\/hsz\/idea\/gitignore\/lexer\/Ignore.flex<\/code>, which doesn\u2019t look like an appropriate place for a resource file.<\/p>\n<p>The Gradle build file defines <a href=\"https:\/\/github.com\/JetBrains\/idea-gitignore\/blob\/c134a21\/build.gradle#L105-L170\" target=\"_blank\" rel=\"noopener\">7 tasks in 65 lines of code<\/a> that are responsible for the process of fetching Grammar-Kit and JFlex outdated binaries (Grammar-Kit v1.2.0.1 was released in November 2014). Over the last 6 years, Grammar-Kit provided a Gradle plugin that simplifies that process to the following form:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"\">val generateLexer = task&lt;GenerateLexer&gt;(&quot;generateLexer&quot;) {\n    source = &quot;src\/main\/grammars\/Ignore.flex&quot;\n    targetDir = &quot;src\/main\/gen\/mobi\/hsz\/idea\/gitignore\/lexer\/&quot;\n    targetClass = &quot;_IgnoreLexer&quot;\n    purgeOldFiles = true\n}\n\nval generateParser = task&lt;GenerateParser&gt;(&quot;generateParser&quot;) {\n    source = &quot;src\/main\/grammars\/Ignore.bnf&quot;\n    targetRoot = &quot;src\/main\/gen&quot;\n    pathToParser = &quot;\/mobi\/hsz\/idea\/gitignore\/IgnoreParser.java&quot;\n    pathToPsiRoot = &quot;\/mobi\/hsz\/idea\/gitignore\/psi&quot;\n    purgeOldFiles = true\n}<\/pre>\n<p>As you may notice, both resource files have been moved to the <code>src\/main\/grammars<\/code> directory to separate them from actual sources.<\/p>\n<p>Below is the <code>sourceSets<\/code> section that redefines our project directory structure:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"\">sourceSets {\n    main {\n        java.srcDirs &#039;src&#039;, &#039;gen&#039;\n        resources.srcDir &#039;resources&#039;\n    }\n    test {\n        java.srcDir &#039;tests&#039;\n        resources.srcDir &#039;testData&#039;\n    }\n}<\/pre>\n<p>Thanks to that definition, our project tree looks like so:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"\">\u251c\u2500\u2500 build\n\u251c\u2500\u2500 gradle\n\u251c\u2500\u2500 resources\n\u251c\u2500\u2500 src\n\u2502   \u2514\u2500\u2500 mobi.hsz.idea.gitignore\n\u251c\u2500\u2500 testData\n\u2502   \u2514\u2500\u2500 inspections\n\u2514\u2500\u2500 tests\n    \u2514\u2500\u2500 mobi.hsz.idea.gitignore<\/pre>\n<p>Plugin sources, resources, tests, and testData are all located right in the root directory. That approach may be confusing for contributors because of its structure, which is different from the default Gradle one. By removing the above sourceSets definition, we can achieve something like:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"\">\u251c\u2500\u2500 build\n\u251c\u2500\u2500 gradle\n\u2514\u2500\u2500 src\n    \u251c\u2500\u2500 main\n    \u2502   \u251c\u2500\u2500 gen\n    |   \u2502   \u2514\u2500\u2500 mobi.hsz.idea.gitignore\n    \u2502   \u251c\u2500\u2500 grammars\n    \u2502   \u251c\u2500\u2500 java\n    |   \u2502   \u2514\u2500\u2500 mobi.hsz.idea.gitignore\n    \u2502   \u2514\u2500\u2500 resources\n    \u2514\u2500\u2500 test\n        \u251c\u2500\u2500 java\n        \u2502   \u2514\u2500\u2500 mobi.hsz.idea.gitignore\n        \u2514\u2500\u2500 resources\n            \u2514\u2500\u2500 inspections<\/pre>\n<p>A bit more nested, but now we can start migrating our plugin sources from Java to Kotlin with ease, file by file, introducing new content in <code>src\/[main|test]\/kotlin<\/code> directories.<\/p>\n<p>The last step of the Gradle script enhancements is to apply all the tools already provided by the <a href=\"https:\/\/github.com\/JetBrains\/intellij-platform-plugin-template\" target=\"_blank\" rel=\"noopener\">IntelliJ Platform Plugin Template<\/a>:<\/p>\n<ul>\n<li>Rewrite existing parts of the Gradle configuration to Kotlin language, and rename the file to <code>gradle.build.kts<\/code>.<\/li>\n<li>Adjust the CHANGELOG.md file to match the common pattern and integrate it with the <a href=\"https:\/\/github.com\/JetBrains\/gradle-changelog-plugin\" target=\"_blank\" rel=\"noopener\">Gradle Changelog Plugin<\/a>.<\/li>\n<\/ul>\n<h3>Continuous Integration<\/h3>\n<p>The Plugin\u2019s CI depends on the external TravisCI service, whose setup is defined within the <code>.travis.yml<\/code> configuration file, but the test phase was recently disabled. Since CI doesn\u2019t even verify the plugin quality or deploy new releases to Marketplace, it\u2019s safe to remove it.<\/p>\n<p>Looking closer at the old gradle.build script, the publishPlugin section of the Gradle IntelliJ Plugin is present. The following <code>publishPlugin<\/code> configuration requires values to be provided in the <code>gradle.properties<\/code> file:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"\">intellij {\n    publishPlugin {\n        username publishUsername\n        token publishToken\n        channels publishChannel\n    }\n}<\/pre>\n<p>A similar approach is already covered in the mentioned Plugin Template that provides a quick-to-adapt GitHub Actions integration:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"\">publishPlugin {\n    token(System.getenv(&quot;PUBLISH_TOKEN&quot;))\n    channels(pluginVersion.split(&#039;-&#039;).getOrElse(1) { &quot;default&quot; }.split(&#039;.&#039;).first())\n}<\/pre>\n<p>First of all, providing a <em>token<\/em>, it is not necessary to define a <em>username<\/em> anymore. The token is set using environment variable \u2013 thanks to this mechanism, we can store our secret value in GitHub Secrets storage and provide it within the GitHub Actions workflow step using:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"\"># Publish the plugin to the Marketplace\n- name: Publish Plugin\nenv:\n  PUBLISH_TOKEN: ${{ secrets.PUBLISH_TOKEN }}\nrun: .\/gradlew publishPlugin<\/pre>\n<p>The value of <em>Channels<\/em> is now set automatically based on the SemVer-like version value, that is, <em>v1.0.0-beta<\/em> will publish our plugin within the <em>beta<\/em> channel.<\/p>\n<p>Both of the GitHub Actions workflow files are documented and prepared ready for use in your project:<\/p>\n<ul>\n<li><a href=\"https:\/\/github.com\/JetBrains\/intellij-platform-plugin-template\/blob\/main\/.github\/workflows\/build.yml\" target=\"_blank\" rel=\"noopener\">.github\/workflows\/build.yml<\/a><\/li>\n<li><a href=\"https:\/\/github.com\/JetBrains\/intellij-platform-plugin-template\/blob\/main\/.github\/workflows\/release.yml\" target=\"_blank\" rel=\"noopener\">.github\/workflows\/release.yml<\/a><\/li>\n<\/ul>\n<h3>Tests<\/h3>\n<p>As the .ignore project already contains some test classes, we should start by migrating them to ensure that further modifications performed in the actual plugin sources will not cause any side effects. Of course, it still depends on the complexity of our tests, and we can\u2019t be 100% sure that the automated conversion will go smoothly, but it\u2019s at least something we can start with.<\/p>\n<p>The first step is to create a kotlin directory with all subdirectories reflecting the current structure in the <code>\/src\/tests\/<\/code> next to the java to move packages quickly.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2021\/01\/plugins-healing-convert-to-kotlin.png\" data-gif-src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2021\/01\/plugins-healing-convert-to-kotlin-1.gif\" alt=\"IntelliJ Platform Explorer\" width=\"770\" class=\"alignnone size-full\" \/><\/p>\n<p>Migrating Java source code to Kotlin is a straightforward operation when you use IntelliJ IDEA. We simply call up the built-in Convert Java File to Kotlin File action (<code>\u2325\u21e7\u2318K<\/code> \/ <code>Ctrl+Alt+Shift+K<\/code>).<\/p>\n<p>During the test verification, most tests seemed to fail. Further investigation reveals that because of the native support for .gitignore and .hgignore, which was introduced in IntelliJ IDEA 2019.2, the tests still invoked inspections and actions using <code>GitFileType<\/code> and <code>GitLanguage<\/code> references.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2021\/01\/plugins-healing-tests.png\" alt=\"\" \/><\/p>\n<p>By swapping <code>GitFileType<\/code> and <code>GitLanguage<\/code> classes with generic ones \u2013 <code>IgnoreFileType<\/code> and <code>IgnoreLanguage<\/code> \u2013 we could fix the issue. As a result, all features provided by plugins can be tested again without conflicting file type support.<\/p>\n<h3>Fixing deprecated APIs<\/h3>\n<p>The IDE actually notifies us about usages of any deprecated APIs, but we\u2019ll use the Plugin Verifier integrated within the Gradle IntelliJ Plugin in order to fully understand the current compatibility status.<\/p>\n<p>The <a href=\"https:\/\/github.com\/JetBrains\/intellij-plugin-verifier\" target=\"_blank\" rel=\"noopener\">IntelliJ Plugin Verifier<\/a> tool, which is also used by Marketplace to verify the plugins against specified IDE versions, provides a full list of the potential issues found in the current implementation, like:<\/p>\n<ul>\n<li>compatibility problems<\/li>\n<li>experimental API usages<\/li>\n<li>internal API usages, and so on<\/li>\n<\/ul>\n<p>The .ignore plugin after the refreshment process will match the latest stable IDE version (2020.3 at the time we\u2019re writing this). The verification process should include the current target IDE and others that may introduce changes in the IntelliJ SDK API. You can obtain the available build versions <a href=\"https:\/\/jb.gg\/intellij-platform-builds-list\" target=\"_blank\" rel=\"noopener\">here<\/a>. Our task configuration will look like this:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"\">intellij {\n    runPluginVerifier {\n        ideVersions(&quot;2020.3, \u2026&quot;)\n    }\n}<\/pre>\n<p>After running the <code>runPluginVerifier<\/code> task, we can collect the following result:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"\">IC-203.5784.10 against mobi.hsz.idea.gitignore:4.0.0: Compatible. 25 usages of deprecated API. 1 usage of internal API\nPlugin mobi.hsz.idea.gitignore:4.0.0 against IC-203.5784.10: Compatible. 25 usages of deprecated API. 1 usage of internal API\nDeprecated API usages (25): \n    #Deprecated method com.intellij.openapi.vfs.VirtualFileManager.addVirtualFileListener(VirtualFileListener) invocation\n        Deprecated method com.intellij.openapi.vfs.VirtualFileManager.addVirtualFileListener(com.intellij.openapi.vfs.VirtualFileListener arg0) : void is invoked in mobi.hsz.idea.gitignore.IgnoreManager.enable() : void\n    #Deprecated class com.intellij.openapi.fileTypes.FileTypeFactory reference\n        Deprecated class com.intellij.openapi.fileTypes.FileTypeFactory is referenced in mobi.hsz.idea.gitignore.file.IgnoreFileTypeFactory\n        Deprecated class com.intellij.openapi.fileTypes.FileTypeFactory is referenced in mobi.hsz.idea.gitignore.file.IgnoreFileTypeFactory.&lt;init&gt;()\n    #Deprecated method com.intellij.openapi.components.BaseComponent.initComponent() is overridden\n        Deprecated method com.intellij.openapi.components.BaseComponent.initComponent() : void is overridden in class mobi.hsz.idea.gitignore.IgnoreFileBasedIndexProjectHandler\n        Deprecated method com.intellij.openapi.components.BaseComponent.initComponent() : void is overridden in class mobi.hsz.idea.gitignore.outer.OuterIgnoreLoaderComponent\n    #Deprecated constructor com.intellij.codeInsight.daemon.LineMarkerInfo.&lt;init&gt;(T, TextRange, Icon, Function, GutterIconNavigationHandler, GutterIconRenderer.Alignment) invocation\n        Deprecated constructor com.intellij.codeInsight.daemon.LineMarkerInfo.&lt;init&gt;(T element, com.intellij.openapi.util.TextRange range, javax.swing.Icon icon, com.intellij.util.Function tooltipProvider, com.intellij.codeInsight.daemon.GutterIconNavigationHandler navHandler, com.intellij.openapi.editor.markup.GutterIconRenderer.Alignment alignment) is invoked in mobi.hsz.idea.gitignore.daemon.IgnoreDirectoryMarkerProvider.getLineMarkerInfo(PsiElement) : LineMarkerInfo\n    #Deprecated interface com.intellij.openapi.components.ProjectComponent reference\n        Deprecated interface com.intellij.openapi.components.ProjectComponent is referenced in mobi.hsz.idea.gitignore.IgnoreManager\n        Deprecated interface com.intellij.openapi.components.ProjectComponent is referenced in mobi.hsz.idea.gitignore.FilesIndexCacheProjectComponent\n        Deprecated interface com.intellij.openapi.components.ProjectComponent is referenced in mobi.hsz.idea.gitignore.IgnoreFileBasedIndexProjectHandler\n        Deprecated interface com.intellij.openapi.components.ProjectComponent is referenced in mobi.hsz.idea.gitignore.outer.OuterIgnoreLoaderComponent\n    #Deprecated method com.intellij.openapi.util.IconLoader.getIcon(String) invocation\n        Deprecated method com.intellij.openapi.util.IconLoader.getIcon(java.lang.String path) : javax.swing.Icon is invoked in mobi.hsz.idea.gitignore.util.Icons.&lt;clinit&gt;() : void\n    #Deprecated method com.intellij.openapi.components.BaseComponent.disposeComponent() is overridden\n        Deprecated method com.intellij.openapi.components.BaseComponent.disposeComponent() : void is overridden in class mobi.hsz.idea.gitignore.IgnoreFileBasedIndexProjectHandler\n        Deprecated method com.intellij.openapi.components.BaseComponent.disposeComponent() : void is overridden in class mobi.hsz.idea.gitignore.IgnoreManager\n        Deprecated method com.intellij.openapi.components.BaseComponent.disposeComponent() : void is overridden in class mobi.hsz.idea.gitignore.outer.OuterIgnoreLoaderComponent\n    #Deprecated method com.intellij.openapi.vfs.VirtualFileManager.removeVirtualFileListener(VirtualFileListener) invocation\n        Deprecated method com.intellij.openapi.vfs.VirtualFileManager.removeVirtualFileListener(com.intellij.openapi.vfs.VirtualFileListener arg0) : void is invoked in mobi.hsz.idea.gitignore.IgnoreManager.disable() : void\n    #Deprecated method com.intellij.util.indexing.FileBasedIndex.registerIndexableSet(IndexableFileSet, Project) invocation\n        Deprecated method com.intellij.util.indexing.FileBasedIndex.registerIndexableSet(com.intellij.util.indexing.IndexableFileSet arg0, com.intellij.openapi.project.Project arg1) : void is invoked in mobi.hsz.idea.gitignore.IgnoreFileBasedIndexProjectHandler$1.run() : void\n    #Deprecated method com.intellij.util.containers.ContainerUtil.newConcurrentMap() invocation\n        Deprecated method com.intellij.util.containers.ContainerUtil.newConcurrentMap() : java.util.concurrent.ConcurrentMap is invoked in mobi.hsz.idea.gitignore.IgnoreManager.&lt;clinit&gt;() : void\n        Deprecated method com.intellij.util.containers.ContainerUtil.newConcurrentMap() : java.util.concurrent.ConcurrentMap is invoked in mobi.hsz.idea.gitignore.IgnoreManager.RefreshTrackedIgnoredRunnable.run(boolean) : void\n    #Deprecated class com.intellij.openapi.fileTypes.StdFileTypes reference\n        Deprecated class com.intellij.openapi.fileTypes.StdFileTypes is referenced in mobi.hsz.idea.gitignore.ui.IgnoreSettingsPanel.TemplatesListPanel.customizeDecorator$1.actionPerformed.descriptor$1.isFileSelectable(VirtualFile) : boolean\n    #Deprecated constructor com.intellij.notification.NotificationGroup.&lt;init&gt;(String, NotificationDisplayType, boolean, String, Icon, int, DefaultConstructorMarker) invocation\n        Deprecated constructor com.intellij.notification.NotificationGroup.&lt;init&gt;(java.lang.String arg0, com.intellij.notification.NotificationDisplayType arg1, boolean arg2, java.lang.String arg3, javax.swing.Icon arg4, int arg5, kotlin.jvm.internal.DefaultConstructorMarker arg6) is invoked in mobi.hsz.idea.gitignore.util.Notify.&lt;clinit&gt;() : void\n    #Deprecated method com.intellij.util.indexing.IndexableFileSet.iterateIndexableFilesIn(VirtualFile, ContentIterator) is overridden\n        Deprecated method com.intellij.util.indexing.IndexableFileSet.iterateIndexableFilesIn(com.intellij.openapi.vfs.VirtualFile file, com.intellij.openapi.roots.ContentIterator iterator) : void is overridden in class mobi.hsz.idea.gitignore.IgnoreFileBasedIndexProjectHandler\n    #Deprecated method com.intellij.util.indexing.FileBasedIndex.removeIndexableSet(IndexableFileSet) invocation\n        Deprecated method com.intellij.util.indexing.FileBasedIndex.removeIndexableSet(com.intellij.util.indexing.IndexableFileSet arg0) : void is invoked in mobi.hsz.idea.gitignore.IgnoreFileBasedIndexProjectHandler.projectListener$1.projectClosing(Project) : void\n    #Deprecated method com.intellij.ide.plugins.PluginManager.getPlugin(PluginId) invocation\n        Deprecated method com.intellij.ide.plugins.PluginManager.getPlugin(com.intellij.openapi.extensions.PluginId id) : com.intellij.ide.plugins.IdeaPluginDescriptor is invoked in mobi.hsz.idea.gitignore.util.Utils.isPluginEnabled(String) : boolean\n    #Deprecated method com.intellij.openapi.startup.StartupManager.registerPreStartupActivity(Runnable) invocation\n        Deprecated method com.intellij.openapi.startup.StartupManager.registerPreStartupActivity(java.lang.Runnable runnable) : void is invoked in mobi.hsz.idea.gitignore.IgnoreFileBasedIndexProjectHandler.&lt;init&gt;(Project, ProjectManager, FileBasedIndex)\n    #Deprecated field com.intellij.openapi.fileTypes.StdFileTypes.XML access\n        Deprecated field com.intellij.openapi.fileTypes.StdFileTypes.XML : com.intellij.openapi.fileTypes.LanguageFileType is accessed in mobi.hsz.idea.gitignore.ui.IgnoreSettingsPanel.TemplatesListPanel.customizeDecorator$1.actionPerformed.descriptor$1.isFileSelectable(VirtualFile) : boolean\nInternal API usages (1): \n    #Internal class com.intellij.ide.plugins.IdeaPluginDescriptorImpl reference\n        Internal class com.intellij.ide.plugins.IdeaPluginDescriptorImpl is referenced in mobi.hsz.idea.gitignore.util.Utils.isPluginEnabled(String) : boolean. This class is marked with @org.jetbrains.annotations.ApiStatus.Internal annotation and indicates that the class is not supposed to be used in client code.\n    Plugin cannot be loaded\/unloaded without IDE restart: Plugin cannot be loaded\/unloaded without IDE restart because it declares project components: `mobi.hsz.idea.gitignore.FilesIndexCacheProjectComponent`, `mobi.hsz.idea.gitignore.IgnoreFileBasedIndexProjectHandler`, `mobi.hsz.idea.gitignore.IgnoreManager`, `mobi.hsz.idea.gitignore.outer.OuterIgnoreLoaderComponent`<\/pre>\n<h3>Deprecated interface ProjectComponent reference<\/h3>\n<p>Plugins that use <a href=\"https:\/\/plugins.jetbrains.com\/docs\/intellij\/plugin-components.html\" target=\"_blank\" rel=\"noopener\">Plugin Components<\/a> do not support <a href=\"https:\/\/plugins.jetbrains.com\/docs\/intellij\/dynamic-plugins.html\" target=\"_blank\" rel=\"noopener\">dynamic loading<\/a>. While some plugins may require an IDE restart, this is not something we wanted for .ignore. Instead, users of this plugin should be able to install\/update\/enable\/disable without needing to restart their IDE \u2013 a smooth process every user would surely enjoy.<\/p>\n<p><code>ProjectComponent<\/code> interface usages were reported for five components in .ignore:<\/p>\n<ol>\n<li><code>IgnoreFileBasedIndexProjectHandler<\/code> \u2013 where the component was refactored to a <code>ProjectManagerListener<\/code>.<br \/>\n<a href=\"https:\/\/github.com\/JetBrains\/idea-gitignore\/commit\/c24dec2cb567a807b6e896aa3076d369d16e40e5\" target=\"_blank\" rel=\"noopener\">c24dec2<\/a><\/li>\n<li><code>OuterIgnoreLoaderComponent<\/code> \u2013 which was simplified from using <code>MessageBus<\/code> to using <code>projectListeners<\/code>.<br \/>\n<a href=\"https:\/\/github.com\/JetBrains\/idea-gitignore\/commit\/cc4dff97788d1a2fae0e68f9ac2b16c86ea332bd\" target=\"_blank\" rel=\"noopener\">cc4dff9<\/a><\/li>\n<li><code>FilesIndexCacheProjectComponent<\/code> \u2013 registered <code>BulkFileListener<\/code> for the <code>VirtualFileManager.VFS_CHANGES<\/code> on the application level, which is an expensive operation that caches too much data. Since the plugin doesn\u2019t support Git and Mercurial, we moved the <code>getFilesForPattern<\/code> method to use utils with no caching enabled (but also with less overhead for this scenario).<br \/>\n<a href=\"https:\/\/github.com\/JetBrains\/idea-gitignore\/commit\/18c8a83d14257bf4af003ec0868b5f672a4f3e60\" target=\"_blank\" rel=\"noopener\">18c8a83<\/a><\/li>\n<li><code>IgnoreManager<\/code> \u2013 a component with exposed <code>getInstance<\/code> method and matcher instance shared across the whole application could be easily transformed into the <code>projectService<\/code> class together with a part of matcher functionality extracted as <code>IgnoreMatcher<\/code>.<br \/>\n<a href=\"https:\/\/github.com\/JetBrains\/idea-gitignore\/commit\/5471f6f2b6d6d0c01cec50e15f9b285b9af6f099\" target=\"_blank\" rel=\"noopener\">5471f6f<\/a><\/li>\n<\/ol>\n<h3>Deprecated class com.intellij.openapi.fileTypes.FileTypeFactory reference<\/h3>\n<p>The .ignore plugin registers multiple file types using the <code>fileTypeFactory<\/code> extension point through the looping of the <code>IgnoreBundle.LANGUAGES<\/code> supported languages collection. That method was convenient, but it was deprecated in 2019.2. See the <a href=\"https:\/\/plugins.jetbrains.com\/docs\/intellij\/registering-file-type.html\" target=\"_blank\" rel=\"noopener\">Registering a File Type<\/a> article for more details.<\/p>\n<p>A fix for that was fairly easy and repetitive: <code>&lt;fileType \/&gt;<\/code> entries were added for each of the supported languages explicitly, with all the information such as language name and associated file extension provided.<br \/>\n<a href=\"https:\/\/github.com\/JetBrains\/idea-gitignore\/commit\/d9ebe8915ea56c954e6bd47d77cca21eaac848cc\" target=\"_blank\" rel=\"noopener\">d9ebe89<\/a><\/p>\n<h3>Deprecated method invocation: VirtualFileManager.addVirtualFileListener(VirtualFileListener)<\/h3>\n<p>The <code>IgnoreManager.enable()<\/code> method adds a new listener to watch for changes in the files tree and clears the cache of calculated entries.<br \/>\n<code>VirtualFileManager.addVirtualFileListener(VirtualFileListener)<\/code> and <code>VirtualFileManager.removeVirtualFileListener(VirtualFileListener)<\/code> were deprecated in 2019.3, so a <code>MessageBus<\/code> with the <code>VFS_CHANGES<\/code> topic should be used instead.<br \/>\n<a href=\"https:\/\/github.com\/JetBrains\/idea-gitignore\/commit\/ef4e22df3626f4a913d2a0fe222cb02f1ef411f2\" target=\"_blank\" rel=\"noopener\">ef4e22d<\/a><\/p>\n<h3>Deprecated constructor com.intellij.codeInsight.daemon.LineMarkerInfo invocation<\/h3>\n<p><code>IgnoreDirectoryMarkerProvider<\/code> provides <code>LineMarkerInfo<\/code> to mark lines with entries recognized as directories with a folder icon. It also requires passing <code>accessibleNameProvider<\/code>, a callback that provides a localized accessible name for the icon (for use by a screen reader).<br \/>\n<a href=\"https:\/\/github.com\/JetBrains\/idea-gitignore\/commit\/46ad2dd251c1db5a51ae87cafd99c25312c3c1fc\" target=\"_blank\" rel=\"noopener\">46ad2dd<\/a><\/p>\n<h3>Deprecated method com.intellij.openapi.util.IconLoader.getIcon(String) invocation<\/h3>\n<p>A quick-fix that requires passing the current Icons class as a second argument.<br \/>\n<a href=\"https:\/\/github.com\/JetBrains\/idea-gitignore\/commit\/50c3fe317d5e09f6826c15ef02e8fa1a562d7d1a\" target=\"_blank\" rel=\"noopener\">50c3fe3<\/a><\/p>\n<h3>Deprecated method com.intellij.ide.plugins.PluginManager.getPlugin(PluginId) invocation<\/h3>\n<p><code>PluginManager.getPlugin<\/code> invokes <code>PluginManagerCore.getPlugin<\/code> \u2013 we just have to call the core class directly.<br \/>\n<a href=\"https:\/\/github.com\/JetBrains\/idea-gitignore\/commit\/b60999e6bafaed65832406858841c9f4b883fd1a\" target=\"_blank\" rel=\"noopener\">b60999e<\/a><\/p>\n<h2>Dynamic Plugin<\/h2>\n<h3>SeveritiesProvider<\/h3>\n<p>The .ignore plugin implements custom localInspections \u2013 one of them inspects and highlights unused entries. If the provided rule is not applied to any file (i.e., ignore non-existing file), it is highlighted with a custom severity level, <code>UNUSED ENTRY<\/code>. Such level is provided explicitly by the <code>&lt;severityProvider&gt;<\/code> extension point, which is not marked with <code>dynamic=\u201dtrue\u201d<\/code> in its definition. Unfortunately, it would prevent us from making the plugin dynamic, and the only option is removing it and setting the localInspection level as <code>WEAK WARNING<\/code>.<br \/>\n<a href=\"https:\/\/github.com\/JetBrains\/idea-gitignore\/commit\/e257ed13\" target=\"_blank\" rel=\"noopener\"> e257ed13<\/a><\/p>\n<h3>Cancel Runnable<\/h3>\n<p><code>IgnoreManager<\/code> service initiates two objects:<\/p>\n<ul>\n<li><code>debouncedStatusesChanged: Debounced<\/code><\/li>\n<li><code>refreshTrackedIgnoredFeature: InterruptibleScheduledFuture<\/code><\/li>\n<\/ul>\n<p>There\u2019s a chance that <code>Runnable<\/code> or <code>ScheduledFuture<\/code> may not be finished before unloading the plugin. Making both <code>Disposable<\/code> and registering their instances with <code>Disposer.register(Disposable parent, Disposable child)<\/code> will let us properly cancel ongoing tasks.<br \/>\n<a href=\"https:\/\/github.com\/JetBrains\/idea-gitignore\/commit\/1b0bbc76\" target=\"_blank\" rel=\"noopener\">1b0bbc76<\/a><\/p>\n<h2>Conclusion<\/h2>\n<p>The .ignore plugin took a lot of time to refactor, and describing the whole process would span much more than one blog post. You\u2019re welcome to review this <a href=\"https:\/\/github.com\/JetBrains\/idea-gitignore\/pull\/646\" target=\"_blank\" rel=\"noopener\">pull request<\/a> for more details. To summarize, here are all of the actions we performed:<\/p>\n<ul>\n<li>Migrating the code to Kotlin and removing the verbose license headers from the source files led to a 60% decrease in code volume, from 644 KB to 262 KB.<\/li>\n<li>All of the compatibility issues were adequately addressed and resolved.<\/li>\n<li>Introducing <em>detekt<\/em> as a static code analysis tool allowed us to keep the code in good shape, which is very important for future project contributions.<\/li>\n<li>Setting up CI based on the <a href=\"https:\/\/jb.gg\/plugin-template\" target=\"_blank\" rel=\"noopener\">IntelliJ Platform Plugin Template<\/a> enhanced the testing and deployment process.<\/li>\n<\/ul>\n<p>And that concludes our first episode of the Revamping Plugins series, and since it\u2019s marked with \u201c#1\u201d, you can expect further articles covering our further endeavors. It is hard to say what we might pick for the next episode, but we\u2019ll try to find something rusty and creaky to provide you with as applicable information as possible.<\/p>\n<p>Stay tuned and follow us on Twitter!<\/p>\n<p><a href=\"https:\/\/twitter.com\/hszanowski\" target=\"_blank\" rel=\"noopener\">Jakub Chrzanowski<\/a> and <a href=\"https:\/\/twitter.com\/JBPlatform\/\" target=\"_blank\" rel=\"noopener\">JetBrains Platform<\/a><\/p>\n","protected":false},"author":1187,"featured_media":108677,"comment_status":"open","ping_status":"closed","template":"","categories":[6815,3989],"tags":[3631,246,6914],"cross-post-tag":[],"acf":[],"_links":{"self":[{"href":"https:\/\/blog.jetbrains.com\/pt-br\/wp-json\/wp\/v2\/platform\/107047"}],"collection":[{"href":"https:\/\/blog.jetbrains.com\/pt-br\/wp-json\/wp\/v2\/platform"}],"about":[{"href":"https:\/\/blog.jetbrains.com\/pt-br\/wp-json\/wp\/v2\/types\/platform"}],"author":[{"embeddable":true,"href":"https:\/\/blog.jetbrains.com\/pt-br\/wp-json\/wp\/v2\/users\/1187"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.jetbrains.com\/pt-br\/wp-json\/wp\/v2\/comments?post=107047"}],"version-history":[{"count":8,"href":"https:\/\/blog.jetbrains.com\/pt-br\/wp-json\/wp\/v2\/platform\/107047\/revisions"}],"predecessor-version":[{"id":112602,"href":"https:\/\/blog.jetbrains.com\/pt-br\/wp-json\/wp\/v2\/platform\/107047\/revisions\/112602"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/blog.jetbrains.com\/pt-br\/wp-json\/wp\/v2\/media\/108677"}],"wp:attachment":[{"href":"https:\/\/blog.jetbrains.com\/pt-br\/wp-json\/wp\/v2\/media?parent=107047"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.jetbrains.com\/pt-br\/wp-json\/wp\/v2\/categories?post=107047"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.jetbrains.com\/pt-br\/wp-json\/wp\/v2\/tags?post=107047"},{"taxonomy":"cross-post-tag","embeddable":true,"href":"https:\/\/blog.jetbrains.com\/pt-br\/wp-json\/wp\/v2\/cross-post-tag?post=107047"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}