📜 ⬆️ ⬇️

Development of IntelliJ IDEA plugin. Part 2

We continue unequal fight with documentation Intellij IDEA. The previous part is here .

Plugin configuration file


All the functionality provided by the plugin, as well as the description and data about the author are listed in the plugin.xml file.
Consider the structure of the file, in which there are almost all significant elements.

<!-- url="..."  URL    (  Welcome Screen    "Plugins") --> <idea-plugin url="http://www.jetbrains.com/idea"> <!--   --> <name>VssIntegration</name> <!--   .       .   ,  -  <name> --> <id>VssIntegration</id> <!--   --> <description>Vss integration plugin</description> <!--     .      Web- --> <change-notes>Initial release of the plugin.</change-notes> <!--   --> <version>1.0</version> <!--  .   "url"     .   "email"     .   "logo"    1616,   JAR --> <vendor url="http://www.jetbrains.com" email="support@jetbrains.com" logo="icons/plugin.png">Foo Inc.</vendor> <!--        --> <depends>MyFirstPlugin</depends> <!--   --> <depends optional="true" config-file="mysecondplugin.xml">MySecondPlugin</depends> <!--       ( JavaHelp )  IDEA.  "file"   JAR   "help".  "path"     helpset. --> <helpset file="myhelp.jar" path="/Help.hs" /> <!--     ,    --> <idea-version since-build="3000" until-build="3999"/> <!--      --> <resource-bundle>messages.MyPluginBundle</resource-bundle> <!--    --> <application-components> <component> <!--  --> <interface-class>com.foo.Component1Interface</interface-class> <!--    --> <implementation-class>com.foo.impl.Component1Impl</implementation-class> </component> </application-components> <!--    --> <project-components> <component> <!--     --> <interface-class>com.foo.Component2</interface-class> <!--  "workspace"   "true",     *.iws   *.ipr (     JDOMExternalizable). --> <option name="workspace" value="true" /> <!--    "loadForDefaultProject",     - --> <loadForDefaultProject> </component> </project-components> <!--    --> <module-components> <component> <interface-class>com.foo.Component3</interface-class> </component> </module-components> <!--  --> <actions> <action id="VssIntegration.GarbageCollection" class="com.foo.impl.CollectGarbage" text="Collect _Garbage" description="Run garbage collector"> <keyboard-shortcut first-keystroke="control alt G" second-keystroke="C" keymap="$default"/> </action> </actions> <!--  ,   .        .  "beanClass"  ,   . --> <extensionPoints> <extensionPoint name="testExtensionPoint" beanClass="com.foo.impl.MyExtensionBean"/> </extensionPoints> <!-- ,      ,    IDEA   .  "defaultExtensionNs"   ID    "com.intellij"     IDEA Core.    <extensions>      --> <extensions xmlns="VssIntegration"> <testExtensionPoint implementation="com.foo.impl.MyExtensionImpl"/> </extensions> </idea-plugin> 

Executing and updating actions


The action system allows plugins to add their own items to the menu and IDEA toolbars. An action is a class inherited from AnAction, whose actionPerformed () method is called when a menu item or the corresponding toolbar button is selected. For example, the same action class is responsible for the menu item “File | Open File ... ”and the“ Open File ”button on the toolbar.

Actions can be combined into groups, which, in turn, may contain subgroups.
Each action or group must be assigned a unique identifier. The identifiers of most standard actions are defined in the com.intellij.openapi.actionSystem.IdeActions class.
Each action can be included in several groups, so that they can be displayed in different places in the user interface. Places where actions can be displayed are defined as constants in the ActionPlaces interface (from the same package). An instance of the Presentation class is created for each seat, so the same action can have a different text or icon in different parts of the interface. Different views are created by copying the return value from the AnAction.getTemplatePresentation () method.

To update the state of the action, the AnAction.update () method is periodically called. An object of type AnActionEvent is passed to the method, giving information about the current context and, in particular, the specific view that should be updated.
')
For information about the current state of the IDE, including the active project, the selected file, the selected text in the editor, etc., the AnActionEvent.getData () method can be used. The various keys accepted by this method are described in the DataKeys class. An object of type AnActionEvent is also passed to the actionPerformed () method.

Action registration


There are two options for registering an action: by listing in the plugin.xml file, or manually at the component initialization stage.
Consider the first option.
 <actions> <!--  <action>  ,    .   "id"    .   "class"   ,  .   "text"      .   "use-shortcut-of"    ,       .   "description"     .   "icon"     . --> <action id="VssIntegration.GarbageCollection" class="com.foo.impl.CollectGarbage" text="Collect _Garbage" description="Run garbage collector" icon="icons/garbage.png"> <!--  <add-to-group> ,        ( ).   "group-id"    .     DefaultActionGroup.   "anchor"    ("first", "last", "before"  "after").  "relative-to-action" ,  anchor  "before"  "after". --> <add-to-group group-id="ToolsMenu" relative-to-action="GenerateJavadoc" anchor="after"/> <!--  <keyboard-shortcut>   .   "first-keystroke"   .     Swing-.   "second-keystroke"   .   "keymap"  .     com.intellij.openapi.keymap.KeymapManager. --> <keyboard-shortcut first-keystroke="control alt G" second-keystroke="C" keymap="$default"/> <!--  <mouse-shortcut>     .     ,  : "button1", "button2", "button3"  ; "shift", "control", "meta", "alt", "altGraph"  ; "doubleClick" –   . --> <mouse-shortcut keystroke="control button3 doubleClick" keymap="$default"/> </action> <!--  <group>  .  <action>, <group>  <separator>      .   "popup" ,    .  popup="true",    ;  popup="false",     . --> <group class="com.foo.impl.MyActionGroup" id="TestActionGroup" text="Test Group" description="Group with test actions" icon="icons/testgroup.png" popup="true"> <action id="VssIntegration.TestAction" class="com.foo.impl.TestAction" text="My Test Action" description="My test action"/> <!--  <separator>    .      <add-to-group>. --> <separator/> <group id="TestActionGroup"/> <!--  <reference>      .   "ref"   . --> <reference ref="EditorCopy"/> <add-to-group group-id="MainMenu" relative-to-action="HelpMenu" anchor="before"/> </group> </actions> 

If you register an action directly from Java code, you should perform two steps. First, an instance of the class derived from AnAction needs to be passed to the ActionManager.registerAction () method to create a binding to the identifier. Next, the action must be added to one or more groups. To obtain an instance of a group by identifier, call ActionManager.getAction () and cast the result to the DefaultActionGroup type.
The following steps are required to record actions during IDEA startup:
  1. create a class that implements the ApplicationComponent interface;
  2. override the getComponentName, initComponent, and disposeComponent methods;
  3. register the class in the <application-components> section of the plugin.xml file.

For clarity, we give an example of code that performs the described procedure.
 public class MyPluginRegistration implements ApplicationComponent { //      @NotNull public String getComponentName() { return "MyPlugin"; } //    MyPluginRegistration   <application-components> //      IDEA. public void initComponent() { ActionManager am = ActionManager.getInstance(); TextBoxes action = new TextBoxes(); //  . am.registerAction("MyPluginAction", action); //  ,      . DefaultActionGroup windowM = (DefaultActionGroup) am.getAction("WindowMenu"); //      . windowM.addSeparator(); windowM.add(action); } //      . public void disposeComponent() { } } 

Creating an interface for action


If the plugin needs to display a toolbar or a popup menu with a specific interface, this can be done using the ActionPopupMenu and ActionToolbar classes. Their objects can be created by calling the ActionManager.createActionPopupMenu () method and ActionManager.createActionToolbar ().

If the action is contained in a specific component (for example, a panel), you usually need to call ActionToolbar.setTargetComponent () and pass an instance of the component as a parameter to it. This ensures that the state of the toolbar buttons depends only on the state of the associated component, and not on the focus on the IDE frame.

Preserving Component State


IntelliJ IDEA provides an API that allows components or services to maintain their state between IDE launches. Both primitive types and composite objects can be saved by using the PersistentStateComponent interface.

Using PropertiesComponent to store simple values

If you need to save only a few primitive types, then the easiest way to achieve this is to use the com.intellij.ide.util.PropertiesComponent service. It can be used to save values ​​at both the application level and the project level (data is placed in the workspace file).
To save the values ​​of the application level, use the PropertiesComponent.getInstance () method, and for the values ​​of the project level, use the PropertiesComponent.getInstance (Project).

Using the PersistentStateComponent Interface

The PersistentStateComponent interface provides more flexible control over persisted values, their format and storage location. In order to use it, you must implement the interface itself in the service or component, determine the class responsible for the state, and specify the storage location using the @State annotation.
It should be noted that extension instances cannot maintain their state using PersistentSomComponent. If necessary, it is recommended to create a separate service responsible for managing the state of this extension.

Implementing the PersistentStateComponent Interface

When implementing the interface, it is necessary to parameterize the class describing the state. The state class can be a regular JavaBean class, or it can be self-implementing PersistentStateComponent.
In the first case, an instance of the state class is usually stored as a field in the component class.
 class MyService implements PersistentStateComponent<MyService.State> { class State { public String value; } State myState; public State getState() { return myState; } public void loadState(State state) { myState = state; } } 

In the second case, you can use the following pattern:
 class MyService implements PersistentStateComponent<MyService> { public String stateValue; public MyService getState() { return this; } public void loadState(MyService state) { XmlSerializerUtil.copyBean(state, this); } } 

State class implementation

Before saving, public fields and properties of a bean class are serialized into an XML format. The following types can be saved:

In order to exclude a public field from the values ​​being serialized, it must be marked with the @Transient annotation.
The state class must have a parameterless constructor that initializes all fields to default values ​​(this is necessary to bring the class into a consistent state before being saved to an XML file).

Determination of storage location

In order to specify where to specifically save data, you should add the @State annotation to the component class, in which you must specify the following fields:

Examples of using the @Storage annotation:

The “id” @Storage annotation can be used to exclude fields when serialized to a specific format. If you do not need to exclude anything, you can assign an arbitrary string value to the parameter.
By defining different values ​​for the file parameter, you can save the state in different files. If you need to determine where to save the values ​​when using the project format based on the directory, you need to add a second @Storage annotation with the “scheme” parameter set to StorageScheme.DIRECTORY_BASED , for example:
 @State( name = "AntConfiguration", storages = { @Storage(id = "default", file = StoragePathMacros.PROJECT_FILE), @Storage(id = "dir", file = StoragePathMacros.PROJECT_CONFIG_DIR + "/ant.xml", scheme = StorageScheme.DIRECTORY_BASED) } ) 


XML format customization

If the persisted state is not displayed on a simple JavaBean, then it is possible to inherit the state class from org.jdom.Element. In this case, the getState () method is used to build an XML element with an arbitrary structure. In the loadState () method, you need to deserialize the JDOM element.
To use standard serialization of bean classes, in conjunction with a custom format, you can use the annotations @Tag, @Attribute, @Property, @MapAnnotation, @AbstractCollection .

In the next part: project structure, VFS.

All articles of the cycle: 1 , 2 , 3 , 4 , 5 , 6 , 7 .

Source: https://habr.com/ru/post/187142/


All Articles