/* * Copyright 2022 The IdeaVim authors * * Use of this source code is governed by an MIT-style * license that can be found in the LICENSE.txt file or at * https://opensource.org/licenses/MIT. */ package com.maddyhome.idea.vim.handler import com.intellij.serviceContainer.BaseKeyedLazyInstance import com.intellij.util.SmartList import com.intellij.util.xmlb.annotations.Attribute import com.maddyhome.idea.vim.command.MappingMode import javax.swing.KeyStroke /** * Action holder for IdeaVim actions. * * [implementation] should be subclass of [EditorActionHandlerBase] * * [modes] ("mappingModes") defines the action modes. E.g. "NO" - action works in normal and op-pending modes. * Warning: V - Visual and Select mode. X - Visual mode. (like vmap and xmap). * Use "ALL" to enable action for all modes. * * [keys] comma-separated list of keys for the action. E.g. `gt,gT` - action gets executed on `gt` or `gT` * Since xml doesn't allow using raw `<` character, use « and » symbols for mappings with modifiers. * E.g. `«C-U»` - CTRL-U (<C-U> in vim notation) * If you want to use exactly `<` character, replace it with `<`. E.g. `i<` - i< * If you want to use comma in mapping, use `«COMMA»` * Do not place a whitespace around the comma! * * * !! IMPORTANT !! * You may wonder why the extension points are used instead of any other approach to register actions. * The reason is startup performance. Using the extension points you don't even have to load classes of actions. * So, all actions are loaded on demand, including classes in classloader. */ class ActionBeanClass : BaseKeyedLazyInstance<EditorActionHandlerBase>() { @Attribute("implementation") var implementation: String? = null @Attribute("mappingModes") var modes: String? = null @Attribute("keys") var keys: String? = null val actionId: String get() = implementation?.let { EditorActionHandlerBase.getActionId(it) } ?: "" fun getParsedKeys(): Set<List<KeyStroke>>? { val myKeys = keys ?: return null val escapedKeys = myKeys.splitByComma() return EditorActionHandlerBase.parseKeysSet(escapedKeys) } override fun getImplementationClassName(): String? = implementation fun getParsedModes(): Set<MappingMode>? { val myModes = modes ?: return null if ("ALL" == myModes) return MappingMode.ALL val res = mutableListOf<MappingMode>() for (c in myModes) { when (c) { 'N' -> res += MappingMode.NORMAL 'X' -> res += MappingMode.VISUAL 'V' -> { res += MappingMode.VISUAL res += MappingMode.SELECT } 'S' -> res += MappingMode.SELECT 'O' -> res += MappingMode.OP_PENDING 'I' -> res += MappingMode.INSERT 'C' -> res += MappingMode.CMD_LINE else -> error("Wrong mapping mode: $c") } } return res.toSet() } private fun String.splitByComma(): List<String> { if (this.isEmpty()) return ArrayList() val res = SmartList<String>() var start = 0 var current = 0 while (current < this.length) { if (this[current] == ',') { res += this.substring(start, current) current++ start = current } current++ } res += this.substring(start, current) return res } }