|
| 1 | +/* |
| 2 | + * Copyright 2003-2026 The IdeaVim authors |
| 3 | + * |
| 4 | + * Use of this source code is governed by an MIT-style |
| 5 | + * license that can be found in the LICENSE.txt file or at |
| 6 | + * https://opensource.org/licenses/MIT. |
| 7 | + */ |
| 8 | + |
| 9 | +package com.maddyhome.idea.vim |
| 10 | + |
| 11 | +import com.intellij.openapi.application.ApplicationManager |
| 12 | +import com.intellij.openapi.components.Service |
| 13 | +import com.intellij.openapi.diagnostic.Logger |
| 14 | +import com.intellij.openapi.keymap.Keymap |
| 15 | +import com.intellij.openapi.keymap.ex.KeymapManagerEx |
| 16 | +import com.intellij.openapi.keymap.impl.DefaultKeymap |
| 17 | +import com.intellij.openapi.ui.Messages |
| 18 | +import com.intellij.openapi.util.SystemInfo |
| 19 | +import com.intellij.util.SlowOperations |
| 20 | +import com.maddyhome.idea.vim.api.VimPluginActivator |
| 21 | +import com.maddyhome.idea.vim.api.injector |
| 22 | +import com.maddyhome.idea.vim.extension.VimExtensionRegistrar |
| 23 | +import com.maddyhome.idea.vim.helper.MacKeyRepeat |
| 24 | +import com.maddyhome.idea.vim.listener.VimListenerManager |
| 25 | +import com.maddyhome.idea.vim.newapi.IjVimSearchGroup |
| 26 | +import com.maddyhome.idea.vim.ui.StatusBarIconFactory |
| 27 | +import com.maddyhome.idea.vim.vimscript.services.VimRcService.executeIdeaVimRc |
| 28 | + |
| 29 | +/** |
| 30 | + * Handles the frontend-facing plugin activation/deactivation lifecycle. |
| 31 | + * |
| 32 | + * This class contains the logic that was previously in VimPlugin.turnOnPlugin/turnOffPlugin. |
| 33 | + * It's separated to decouple VimPlugin.java from frontend-only classes like VimListenerManager, |
| 34 | + * RegisterActions, StatusBarIconFactory, etc. |
| 35 | + * |
| 36 | + * This class (and its dependencies) can be moved to the frontend module once the split is complete. |
| 37 | + */ |
| 38 | +@Service |
| 39 | +internal class IjVimPluginActivator : VimPluginActivator { |
| 40 | + |
| 41 | + private val log = Logger.getInstance(IjVimPluginActivator::class.java) |
| 42 | + private var stateUpdated = false |
| 43 | + |
| 44 | + /** |
| 45 | + * IdeaVim plugin activation. |
| 46 | + * This is an important operation and some commands ordering should be preserved. |
| 47 | + * Please make sure that the documentation of this function is in sync with the code. |
| 48 | + * |
| 49 | + * 1. Update state - schedules a state update (shows dialogs to the user) |
| 50 | + * 2. Command registration - BEFORE ~/.ideavimrc execution |
| 51 | + * 2.1 Register vim actions in command mode |
| 52 | + * 2.2 Register extensions |
| 53 | + * 2.3 Register functions |
| 54 | + * 3. Options initialisation |
| 55 | + * 4. ~/.ideavimrc execution |
| 56 | + * 5. Components initialization - AFTER ideavimrc execution |
| 57 | + * (VimListenerManager accesses `number` option and guicaret) |
| 58 | + */ |
| 59 | + override fun activate() { |
| 60 | + // 1) Update state |
| 61 | + ApplicationManager.getApplication().invokeLater(this::updateState) |
| 62 | + |
| 63 | + // 2) Command registration |
| 64 | + // 2.1) Register vim actions in command mode |
| 65 | + RegisterActions.registerActions() |
| 66 | + |
| 67 | + // 2.2) Register extensions |
| 68 | + VimExtensionRegistrar.registerExtensions() |
| 69 | + |
| 70 | + // 2.3) Register functions |
| 71 | + injector.functionService.registerHandlers() |
| 72 | + |
| 73 | + // 3) Option initialisation |
| 74 | + injector.optionGroup.initialiseOptions() |
| 75 | + |
| 76 | + // 4) ~/.ideavimrc execution |
| 77 | + // Evaluate in the context of the fallback window, to capture local option state, to copy to the first editor window |
| 78 | + try { |
| 79 | + SlowOperations.knownIssue("VIM-3661").use { |
| 80 | + registerIdeavimrc() |
| 81 | + } |
| 82 | + } catch (e: Exception) { |
| 83 | + log.error("Failed to register ideavimrc", e) |
| 84 | + } |
| 85 | + |
| 86 | + // 5) Turning on should be performed after all commands registration |
| 87 | + VimPlugin.getSearch().turnOn() |
| 88 | + VimListenerManager.turnOn() |
| 89 | + } |
| 90 | + |
| 91 | + override fun deactivate(unsubscribe: Boolean) { |
| 92 | + val searchGroup: IjVimSearchGroup? = VimPlugin.getSearchIfCreated() |
| 93 | + searchGroup?.turnOff() |
| 94 | + |
| 95 | + if (unsubscribe) { |
| 96 | + VimListenerManager.turnOff() |
| 97 | + } |
| 98 | + |
| 99 | + // Use getServiceIfCreated to avoid creating the service during the dispose (this is prohibited by the platform) |
| 100 | + ApplicationManager.getApplication() |
| 101 | + .getServiceIfCreated(com.maddyhome.idea.vim.api.VimCommandLineService::class.java) |
| 102 | + ?.fullReset() |
| 103 | + |
| 104 | + // Unregister vim actions in command mode |
| 105 | + RegisterActions.unregisterActions() |
| 106 | + } |
| 107 | + |
| 108 | + override fun updateStatusBarIcon() { |
| 109 | + StatusBarIconFactory.Util.updateIcon() |
| 110 | + } |
| 111 | + |
| 112 | + private var ideavimrcRegistered = false |
| 113 | + |
| 114 | + private fun registerIdeavimrc() { |
| 115 | + if (ideavimrcRegistered) return |
| 116 | + ideavimrcRegistered = true |
| 117 | + |
| 118 | + if (!ApplicationManager.getApplication().isUnitTestMode) { |
| 119 | + try { |
| 120 | + injector.optionGroup.startInitVimRc() |
| 121 | + executeIdeaVimRc(injector.fallbackWindow) |
| 122 | + } finally { |
| 123 | + injector.optionGroup.endInitVimRc() |
| 124 | + } |
| 125 | + } |
| 126 | + } |
| 127 | + |
| 128 | + private fun updateState() { |
| 129 | + if (stateUpdated) return |
| 130 | + if (VimPlugin.isEnabled() && !ApplicationManager.getApplication().isUnitTestMode) { |
| 131 | + stateUpdated = true |
| 132 | + if (SystemInfo.isMac) { |
| 133 | + val enabled = MacKeyRepeat.isEnabled |
| 134 | + val isKeyRepeat = VimPlugin.getEditor().isKeyRepeat() |
| 135 | + if ((enabled == null || !enabled) && (isKeyRepeat == null || isKeyRepeat)) { |
| 136 | + // This system property is used in IJ ui robot to hide the startup tips |
| 137 | + val showNotification = |
| 138 | + System.getProperty("ide.show.tips.on.startup.default.value", "true").toBoolean() |
| 139 | + log.info("Do not show mac repeat notification because ide.show.tips.on.startup.default.value=false") |
| 140 | + if (showNotification) { |
| 141 | + if (VimPlugin.getNotifications(null).enableRepeatingMode() == Messages.YES) { |
| 142 | + VimPlugin.getEditor().setKeyRepeat(true) |
| 143 | + MacKeyRepeat.isEnabled = true |
| 144 | + } else { |
| 145 | + VimPlugin.getEditor().setKeyRepeat(false) |
| 146 | + } |
| 147 | + } |
| 148 | + } |
| 149 | + } |
| 150 | + |
| 151 | + val previousStateVersion = VimPlugin.getInstance().previousStateVersion |
| 152 | + val previousKeyMap = VimPlugin.getInstance().previousKeyMap |
| 153 | + |
| 154 | + if (previousStateVersion > 0 && previousStateVersion < 3) { |
| 155 | + val manager = KeymapManagerEx.getInstanceEx() |
| 156 | + var keymap: Keymap? = null |
| 157 | + if (previousKeyMap != null) { |
| 158 | + keymap = manager.getKeymap(previousKeyMap) |
| 159 | + } |
| 160 | + if (keymap == null) { |
| 161 | + keymap = manager.getKeymap(DefaultKeymap.getInstance().defaultKeymapName) |
| 162 | + } |
| 163 | + assert(keymap != null) { "Default keymap not found" } |
| 164 | + manager.activeKeymap = keymap!! |
| 165 | + } |
| 166 | + if (previousStateVersion > 0 && previousStateVersion < 4) { |
| 167 | + VimPlugin.getNotifications(null).noVimrcAsDefault() |
| 168 | + } |
| 169 | + } |
| 170 | + } |
| 171 | +} |
0 commit comments