diff --git a/README.md b/README.md index d16ef76..48ef5e8 100644 --- a/README.md +++ b/README.md @@ -7,8 +7,8 @@ # idea-plugin-example2 -This repo is a pedagogical example of an IDEA plugin. To learn more about plugins please read the tutorial that -accompanies this code example on developerlife.com - +This repo is a pedagogical example of an IDEA plugin. To learn more about plugins please read the +tutorial that accompanies this code example on developerlife.com - [Introduction to creating IntelliJ IDEA plugins](http://localhost:4000/2020/11/20/idea-plugin-example-intro/). Also checkout the accompanying repo to this one diff --git a/docs/linemarkerprovider.md b/docs/linemarkerprovider.md index d41df8a..cec14f1 100644 --- a/docs/linemarkerprovider.md +++ b/docs/linemarkerprovider.md @@ -14,35 +14,39 @@ ## Line marker provider -Line marker providers allow your plugin to display an icon in the gutter of an editor window. You can also provide -actions that can be run when the user interacts with the gutter icon, along with a tooltip that can be generated when -the user hovers over the gutter icon. +Line marker providers allow your plugin to display an icon in the gutter of an editor window. You +can also provide actions that can be run when the user interacts with the gutter icon, along with a +tooltip that can be generated when the user hovers over the gutter icon. In order to use line marker providers, you have to do two things: -1. Create a class that implements `LineMarkerProvider` that generates the `LineMarkerInfo` for the correct `PsiElement` - that you want IDEA to highlight in the IDE. +1. Create a class that implements `LineMarkerProvider` that generates the `LineMarkerInfo` for the + correct `PsiElement` that you want IDEA to highlight in the IDE. 2. Register this provider in `plugin.xml` and associate it to be run for a specific language. -When your plugin is loaded, IDEA will then run your line marker provider when a file of that language type is loaded in -the editor. This happens in two passes for performance reasons. +When your plugin is loaded, IDEA will then run your line marker provider when a file of that +language type is loaded in the editor. This happens in two passes for performance reasons. -1. IDEA will first call your provider implementation with the `PsiElements` that are currently visible. -2. IDEA will then call your provider implementation with the `PsiElements` that are currently hidden. +1. IDEA will first call your provider implementation with the `PsiElements` that are currently + visible. +2. IDEA will then call your provider implementation with the `PsiElements` that are currently + hidden. -It is very important that you only return a `LineMarkerInfo` for the more specific `PsiElement` that you wish IDEA to -highlight, as if you scope it too broadly, there will be scenarios where your gutter icon will blink! Here's a detailed -explanation as to why (a comment from the source for +It is very important that you only return a `LineMarkerInfo` for the more specific `PsiElement` that +you wish IDEA to highlight, as if you scope it too broadly, there will be scenarios where your +gutter icon will blink! Here's a detailed explanation as to why (a comment from the source for [`LineMarkerProvider.java: source file`](https://github.com/JetBrains/intellij-community/blob/master/platform/lang-api/src/com/intellij/codeInsight/daemon/LineMarkerProvider.java)). -> Please create line marker info for leaf elements only - i.e. the smallest possible elements. For example, instead of -> returning method marker for `PsiMethod`, create the marker for the `PsiIdentifier` which is a name of this method. +> Please create line marker info for leaf elements only - i.e. the smallest possible elements. For +> example, instead of returning method marker for `PsiMethod`, create the marker for the +> `PsiIdentifier` which is a name of this method. > -> Highlighting (specifically, `LineMarkersPass`) queries all `LineMarkerProvider`s in two passes (for performance -> reasons): +> Highlighting (specifically, `LineMarkersPass`) queries all `LineMarkerProvider`s in two passes +> (for performance reasons): > > 1. first pass for all elements in visible area -> 2. second pass for all the rest elements If provider returned nothing for both areas, its line markers are cleared. +> 2. second pass for all the rest elements If provider returned nothing for both areas, its line +> markers are cleared. > > So imagine a `LineMarkerProvider` which (incorrectly) written like this: > @@ -60,12 +64,12 @@ explanation as to why (a comment from the source for > } > ``` > -> Note that it create `LineMarkerInfo` for the whole method body. Following will happen when this method is half-visible -> (e.g. its name is visible but a part of its body isn't): +> Note that it create `LineMarkerInfo` for the whole method body. Following will happen when this +> method is half-visible (e.g. its name is visible but a part of its body isn't): > > 1. the first pass would remove line marker info because the whole `PsiMethod` isn't visible -> 2. the second pass would try to add line marker info back because `LineMarkerProvider` was called for the `PsiMethod` -> at last +> 2. the second pass would try to add line marker info back because `LineMarkerProvider` was called +> for the `PsiMethod` at last > > As a result, line marker icon will blink annoyingly. Instead, write this: > @@ -87,13 +91,14 @@ explanation as to why (a comment from the source for ### Example of a provider for Markdown language -Let's say that for Markdown files that are open in the IDE, we want to highlight any lines that have links in them. We -want an icon to show up in the gutter area that the user can see and click on to take some actions. For example, they -can open the link. +Let's say that for Markdown files that are open in the IDE, we want to highlight any lines that have +links in them. We want an icon to show up in the gutter area that the user can see and click on to +take some actions. For example, they can open the link. #### 1. Declare dependencies -Also, because we are relying on the Markdown plugin, in our plugin, we have to add the following dependencies. +Also, because we are relying on the Markdown plugin, in our plugin, we have to add the following +dependencies. To `plugin.xml`, we must add. @@ -141,9 +146,9 @@ The first thing we need to do is register our line marker provider in `plugin.xm #### 3. Provide an implementation of LineMarkerProvider -Then we have to provide an implementation of `LineMarkerProvider` that returns a `LineMarkerInfo` for the most fine -grained `PsiElement` that it successfully matches against. In other words, we can either match against the -`LINK_DESTINATION` or the `LINK_TEXT` elements. +Then we have to provide an implementation of `LineMarkerProvider` that returns a `LineMarkerInfo` +for the most fine grained `PsiElement` that it successfully matches against. In other words, we can +either match against the `LINK_DESTINATION` or the `LINK_TEXT` elements. Here's an example of what a Markdown link looks like (from a PSI perspective) for the string `[`LineMarkerProvider.java`](https://github.com/JetBrains/intellij-community/blob/master/platform/lang-api/src/com/intellij/codeInsight/daemon/LineMarkerProvider.java)`. @@ -163,7 +168,8 @@ ASTWrapperPsiElement(Markdown:Markdown:INLINE_LINK)(1644,1810) PsiElement(Markdown:Markdown:))(')')(1809,1810) ``` -Here's what the implementation of the line marker provider that matches `INLINE_LINK` might look like. +Here's what the implementation of the line marker provider that matches `INLINE_LINK` might look +like. ```kotlin package ui @@ -196,8 +202,8 @@ internal class MarkdownLineMarkerProvider : LineMarkerProvider { } ``` -You can add the `ic_linemarkerprovider.svg` icon here (create this file in the `$PROJECT_DIR/src/main/resources/icons/` -folder. +You can add the `ic_linemarkerprovider.svg` icon here (create this file in the +`$PROJECT_DIR/src/main/resources/icons/` folder. ```xml @@ -209,24 +215,28 @@ folder. #### 4. Provide a more complex implementation of LineMarkerProvider -The example we have so far, simply shows a gutter icon beside the lines in the editor window, that match our matching -criteria. Let's say that we want to show some relevant actions that can be performed on the `PsiElement`(s) that matched -and are associated with the gutter icon. In this case we have to delve a little deeper into the `LineMarkerInfo` class. +The example we have so far, simply shows a gutter icon beside the lines in the editor window, that +match our matching criteria. Let's say that we want to show some relevant actions that can be +performed on the `PsiElement`(s) that matched and are associated with the gutter icon. In this case +we have to delve a little deeper into the `LineMarkerInfo` class. If you look at [`LineMarkerInfo.java`](https://github.com/jetbrains/intellij-community/blob/master/platform/lang-api/src/com/intellij/codeInsight/daemon/LineMarkerInfo.java#L137), -you will find a `createGutterRenderer()` method. We can actually override this method and create our own -`GutterIconRenderer` objects that have an action group inside of them which will hold all our related actions. +you will find a `createGutterRenderer()` method. We can actually override this method and create our +own `GutterIconRenderer` objects that have an action group inside of them which will hold all our +related actions. The following class [`RunLineMarkerProvider.java`](https://github.com/jetbrains/intellij-community/blob/master/platform/execution-impl/src/com/intellij/execution/lineMarker/RunLineMarkerProvider.java#L115) -actually provides us some clue of how to use all of this. In IDEA, when there are targets that you can run, a gutter -icon (play button) that allows you to execute the run target. This class actually provides an implementation of that -functionality. Using it as inspiration, we can create the more complex version of our line marker provider. +actually provides us some clue of how to use all of this. In IDEA, when there are targets that you +can run, a gutter icon (play button) that allows you to execute the run target. This class actually +provides an implementation of that functionality. Using it as inspiration, we can create the more +complex version of our line marker provider. -We are going to change our initial implementation of `MarkdownLineMarkerProvider` quite drastically. First we have to -add a class that is our new `LineMarkerInfo` implementation called `RunLineMarkerInfo`. This class simply allows us to -return an `ActionGroup` that we will now have to provide. +We are going to change our initial implementation of `MarkdownLineMarkerProvider` quite drastically. +First we have to add a class that is our new `LineMarkerInfo` implementation called +`RunLineMarkerInfo`. This class simply allows us to return an `ActionGroup` that we will now have to +provide. ```kotlin class RunLineMarkerInfo(element: PsiElement, @@ -299,15 +309,16 @@ class MarkdownLineMarkerProvider : LineMarkerProvider { } ``` -The `createActionGroup(...)` method actually creates an `ActionGroup` and adds a bunch of actions that will be available -when the user clicks on the gutter icon for this plugin. Note that you can also add actions that are registered in your -`plugin.xml` using something like this. +The `createActionGroup(...)` method actually creates an `ActionGroup` and adds a bunch of actions +that will be available when the user clicks on the gutter icon for this plugin. Note that you can +also add actions that are registered in your `plugin.xml` using something like this. ```kotlin group.add(ActionManager.getInstance().getAction("ID of your plugin action")) ``` -Finally, here's the action to open a URL that is associated with the INLINE_LINK that is highlighted in the gutter. +Finally, here's the action to open a URL that is associated with the INLINE_LINK that is highlighted +in the gutter. ```kotlin class OpenUrlAction(val linkDestination: String?) : diff --git a/docs/toolwindows.md b/docs/toolwindows.md index b1bbc66..aeb641a 100644 --- a/docs/toolwindows.md +++ b/docs/toolwindows.md @@ -17,10 +17,11 @@ For both of these types of tool windows, the following applies: 1. Each tool window can have multiple tabs (aka "contents"). -2. Each side of the IDE can only show 2 tool windows at any given time, as the primary or the secondary. For eg: you can - move the "Project" tool window to "Left Top", and move the "Structure" tool window to "Left Bottom". This way you can - open both of them at the same time. Note that when you move these tool windows to "Left Top" or "Left Bottom" how - they actually move to the top or bottom of the side of the IDE. +2. Each side of the IDE can only show 2 tool windows at any given time, as the primary or the + secondary. For eg: you can move the "Project" tool window to "Left Top", and move the "Structure" + tool window to "Left Bottom". This way you can open both of them at the same time. Note that when + you move these tool windows to "Left Top" or "Left Bottom" how they actually move to the top or + bottom of the side of the IDE. There are two main types of tool windows: 1) Declarative, and 2) Programmatic. @@ -28,17 +29,20 @@ There are two main types of tool windows: 1) Declarative, and 2) Programmatic. Always visible and the user can interact with it at anytime (eg: Gradle plugin tool window). -- This type of tool window must be registered in `plugin.xml` using the `com.intellij.toolWindow` extension point. You - can specify things to register this in XML: +- This type of tool window must be registered in `plugin.xml` using the `com.intellij.toolWindow` + extension point. You can specify things to register this in XML: - `id`: Text displayed in the tool window button. - - `anchor`: Side of the screen in which the tool window is displayed ("left", "right", or "bottom"). + - `anchor`: Side of the screen in which the tool window is displayed ("left", "right", or + "bottom"). - `secondary`: Specify whether it is displayed in the primary or secondary group. - `icon`: Icon displayed in the tool window button (13px x 13px). - - `factoryClass`: A class implementing `ToolWindowFactory` interface, which is used to instantiate the tool window - when the user clicks on the tool window button (by calling `createToolWindowContent()`). Note that if a user does - not interact with the button, then a tool window doesn't get created. - - For versions 2020.1 and later, also implement the `isApplicable(Project)` method if there's no need to display a - tool window for all projects. Note this condition is only evaluated the first time a project is loaded. + - `factoryClass`: A class implementing `ToolWindowFactory` interface, which is used to instantiate + the tool window when the user clicks on the tool window button (by calling + `createToolWindowContent()`). Note that if a user does not interact with the button, then a tool + window doesn't get created. + - For versions 2020.1 and later, also implement the `isApplicable(Project)` method if there's no + need to display a tool window for all projects. Note this condition is only evaluated the first + time a project is loaded. Here's an example. @@ -80,20 +84,21 @@ The `plugin.xml` snippet. ### 2. Programmatic tool window -Only visible when a plugin creates it to show the results of an operation (eg: Analyze Dependencies action). This type -of tool window must be added programmatically by calling +Only visible when a plugin creates it to show the results of an operation (eg: Analyze Dependencies +action). This type of tool window must be added programmatically by calling `ToolWindowManager.getInstance().registerToolWindow(RegisterToolWindowTask)`. A couple of things to remember. -1. You have to register the tool window (w/ the tool window manager) before using it. This is a one time operation. - There's no need to register the tool window if it's already been registered. Registering simply shows the tool window - in the IDEA UI. Unregistering removes it from the UI. +1. You have to register the tool window (w/ the tool window manager) before using it. This is a one + time operation. There's no need to register the tool window if it's already been registered. + Registering simply shows the tool window in the IDEA UI. Unregistering removes it from the UI. 2. You can tell the tool window to auto hide itself when there are no contents inside of it. -3. You can create as many "contents" as you want and add it to the tool window. Each content is basically a tab. You can - also specify that the content is closable. -4. You can also attach a disposer to a content so that you can take some action when the content or tab is closed. For - eg you can just unregister the tool window when there are no contents left in the tool window. +3. You can create as many "contents" as you want and add it to the tool window. Each content is + basically a tab. You can also specify that the content is closable. +4. You can also attach a disposer to a content so that you can take some action when the content or + tab is closed. For eg you can just unregister the tool window when there are no contents left in + the tool window. Here's an example of all of the things listed above. @@ -162,15 +167,16 @@ The `plugin.xml` snippet, to register the action. ### Indices and dumb aware -Displaying the contents of many tool windows requires access to the indices. Because of that, tool windows are normally -disabled while building indices, unless true is passed as the value of `canWorkInDumbMode` to the `registerToolWindow()` -function (for programmatic tool windows). You can also implement `DumbAware` in your factory class to let IDEA know that -your tool window can be shown while indices are being built. +Displaying the contents of many tool windows requires access to the indices. Because of that, tool +windows are normally disabled while building indices, unless true is passed as the value of +`canWorkInDumbMode` to the `registerToolWindow()` function (for programmatic tool windows). You can +also implement `DumbAware` in your factory class to let IDEA know that your tool window can be shown +while indices are being built. ### Creating a content for any kind of tool window -Regardless of the type of tool window (declarative or programmatic) here is the sequence of operations that you have to -perform in order to add a content: +Regardless of the type of tool window (declarative or programmatic) here is the sequence of +operations that you have to perform in order to add a content: 1. Call `ToolWindow.getContentManager()` to get all the contents of a tool window. Eg: `val contentManager: ContentManager = toolWindow.contentManager`. @@ -181,12 +187,15 @@ perform in order to add a content: ### Content closeability -A plugin can control whether the user is allowed to close tabs either 1) globally or 2) on a per content basis. +A plugin can control whether the user is allowed to close tabs either 1) globally or 2) on a per +content basis. -1. **Globally**: This is done by passing the `canCloseContents` parameter to the `registerToolWindow()` function, or by - specifying `canCloseContents="true"` in `plugin.xml`. The default value is `false`. Note that calling - `setClosable(true)` on `ContentManager` content will be ignored unless `canCloseContents` is explicitly set. -2. **Per content basis**: This is done by calling `setCloseable(Boolean)` on each content object itself. +1. **Globally**: This is done by passing the `canCloseContents` parameter to the + `registerToolWindow()` function, or by specifying `canCloseContents="true"` in `plugin.xml`. The + default value is `false`. Note that calling `setClosable(true)` on `ContentManager` content will + be ignored unless `canCloseContents` is explicitly set. +2. **Per content basis**: This is done by calling `setCloseable(Boolean)` on each content object + itself. If closing tabs is enabled in general, a plugin can disable closing of specific tabs by calling `Content.setCloseable(false)`.