|
| 1 | +# Actions, listeners, notifications, tool windows |
| 2 | + |
| 3 | +## Actions |
| 4 | + |
| 5 | +Official docs: <https://plugins.jetbrains.com/docs/intellij/basic-action-system.html> |
| 6 | + |
| 7 | +```kotlin |
| 8 | +class RefreshPageAction : AnAction() { |
| 9 | + override fun getActionUpdateThread() = ActionUpdateThread.BGT |
| 10 | + |
| 11 | + override fun update(e: AnActionEvent) { |
| 12 | + e.presentation.isEnabled = e.project != null |
| 13 | + } |
| 14 | + |
| 15 | + override fun actionPerformed(e: AnActionEvent) { |
| 16 | + val project = e.project ?: return |
| 17 | + project.service<TemporalWebUIPanel>().reload() |
| 18 | + } |
| 19 | +} |
| 20 | +``` |
| 21 | + |
| 22 | +Registration: |
| 23 | + |
| 24 | +```xml |
| 25 | +<actions> |
| 26 | + <action id="Temporal.RefreshPage" |
| 27 | + class="com.example.my.RefreshPageAction" |
| 28 | + text="Refresh" description="Refresh the Temporal UI" |
| 29 | + icon="AllIcons.Actions.Refresh"> |
| 30 | + <add-to-group group-id="ToolWindowContextMenu" anchor="last"/> |
| 31 | + <keyboard-shortcut keymap="$default" first-keystroke="ctrl alt R"/> |
| 32 | + </action> |
| 33 | +</actions> |
| 34 | +``` |
| 35 | + |
| 36 | +Rules: |
| 37 | +- **Always override `getActionUpdateThread()`** — pick `BGT` when `update()` |
| 38 | + reads project/PSI state; `EDT` only for pure UI checks. Using BGT avoids |
| 39 | + freezes. |
| 40 | +- `update()` must be fast. Offload work to `actionPerformed`. |
| 41 | +- Define a `groupId` via `<group id="..." class="com.intellij.openapi.actionSystem.DefaultActionGroup">` |
| 42 | + if you need a submenu; add actions to it with `<add-to-group>`. |
| 43 | + |
| 44 | +## Listeners & MessageBus |
| 45 | + |
| 46 | +Official docs: <https://plugins.jetbrains.com/docs/intellij/plugin-listeners.html> |
| 47 | + |
| 48 | +Declarative (preferred — lazy, no startup cost): |
| 49 | + |
| 50 | +```xml |
| 51 | +<applicationListeners> |
| 52 | + <listener class="com.example.my.MyAppListener" |
| 53 | + topic="com.intellij.openapi.application.ApplicationActivationListener"/> |
| 54 | +</applicationListeners> |
| 55 | + |
| 56 | +<projectListeners> |
| 57 | + <listener class="com.example.my.MyProjectListener" |
| 58 | + topic="com.intellij.openapi.vfs.newvfs.BulkFileListener"/> |
| 59 | +</projectListeners> |
| 60 | +``` |
| 61 | + |
| 62 | +Programmatic: |
| 63 | + |
| 64 | +```kotlin |
| 65 | +project.messageBus.connect(parentDisposable) |
| 66 | + .subscribe(BulkFileListener.TOPIC, object : BulkFileListener { |
| 67 | + override fun after(events: MutableList<out VFileEvent>) { /* ... */ } |
| 68 | + }) |
| 69 | +``` |
| 70 | + |
| 71 | +Custom topics: |
| 72 | + |
| 73 | +```kotlin |
| 74 | +interface ServerListener { |
| 75 | + fun onServerStarted(event: ServerStarted) |
| 76 | + companion object { |
| 77 | + @Topic.ProjectLevel |
| 78 | + val TOPIC: Topic<ServerListener> = Topic.create("Temporal server", ServerListener::class.java) |
| 79 | + } |
| 80 | +} |
| 81 | +``` |
| 82 | + |
| 83 | +Rules: |
| 84 | +- Listener implementations must be **stateless**; persist state in services. |
| 85 | +- Always `connect(parentDisposable)` programmatically — orphan connections |
| 86 | + leak. |
| 87 | + |
| 88 | +## Notifications |
| 89 | + |
| 90 | +```xml |
| 91 | +<extensions defaultExtensionNs="com.intellij"> |
| 92 | + <notificationGroup id="Temporal" |
| 93 | + displayType="BALLOON" |
| 94 | + isLogByDefault="true" |
| 95 | + bundle="messages.TemporalBundle" |
| 96 | + key="notification.group"/> |
| 97 | +</extensions> |
| 98 | +``` |
| 99 | + |
| 100 | +`displayType`: `BALLOON` (transient popup), `STICKY_BALLOON` (stays until |
| 101 | +dismissed), `TOOL_WINDOW` (shown inside the target tool window, needs |
| 102 | +`toolWindowId=`), `NONE` (event log only). |
| 103 | + |
| 104 | +Emit: |
| 105 | + |
| 106 | +```kotlin |
| 107 | +NotificationGroupManager.getInstance() |
| 108 | + .getNotificationGroup("Temporal") |
| 109 | + .createNotification( |
| 110 | + TemporalBundle.message("notification.server.started.title"), |
| 111 | + TemporalBundle.message("notification.server.started.content", port), |
| 112 | + NotificationType.INFORMATION, |
| 113 | + ) |
| 114 | + .notify(project) |
| 115 | +``` |
| 116 | + |
| 117 | +## Tool windows |
| 118 | + |
| 119 | +Official docs: <https://plugins.jetbrains.com/docs/intellij/tool-windows.html> |
| 120 | + |
| 121 | +```xml |
| 122 | +<toolWindow id="Temporal" |
| 123 | + icon="/icons/temporal/icon.svg" |
| 124 | + anchor="right" |
| 125 | + factoryClass="com.example.my.TemporalWindowFactory" |
| 126 | + doNotActivateOnStart="true" |
| 127 | + secondary="false"/> |
| 128 | +``` |
| 129 | + |
| 130 | +```kotlin |
| 131 | +class TemporalWindowFactory : ToolWindowFactory, DumbAware { |
| 132 | + override fun createToolWindowContent(project: Project, toolWindow: ToolWindow) { |
| 133 | + val panel = TemporalWebUIPanel(project) |
| 134 | + val content = toolWindow.contentManager.factory.createContent(panel, "Web UI", false) |
| 135 | + toolWindow.contentManager.addContent(content) |
| 136 | + } |
| 137 | + |
| 138 | + override fun isApplicable(project: Project) = true // gate visibility |
| 139 | +} |
| 140 | +``` |
| 141 | + |
| 142 | +Retrieve at runtime: |
| 143 | + |
| 144 | +```kotlin |
| 145 | +ToolWindowManager.getInstance(project).getToolWindow("Temporal")?.show() |
| 146 | +``` |
| 147 | + |
| 148 | +Rules: |
| 149 | +- Implement `DumbAware` unless the tool window genuinely needs indexes. |
| 150 | +- `createToolWindowContent` runs on EDT — keep it cheap; defer heavy UI wiring |
| 151 | + to a background task or to a button click inside the panel. |
0 commit comments