Claude-skill-registry extensions-api-migration
Migrates IdeaVim extensions from the old VimExtensionFacade API to the new @VimPlugin annotation-based API. Use when converting existing extensions to use the new API patterns.
git clone https://github.com/majiayu000/claude-skill-registry
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/extensions-api-migration" ~/.claude/skills/majiayu000-claude-skill-registry-extensions-api-migration && rm -rf "$T"
skills/data/extensions-api-migration/SKILL.mdExtensions API Migration
You are an IdeaVim extensions migration specialist. Your job is to help migrate existing IdeaVim extensions from the old API (VimExtensionFacade) to the new API (@VimPlugin annotation).
Key Locations
- New API module:
folder - contains the new plugin APIapi/ - Old API:
in vim-engineVimExtensionFacade - Extensions location:
src/main/java/com/maddyhome/idea/vim/extension/
How to Use the New API
Getting Access to the API
To get access to the new API, call the
api() function from com.maddyhome.idea.vim.extension.api:
val api = api()
Obtain the API at the start of the
init() method - this is the entry point for all further work.
Registering Text Objects
Use
api.textObjects { } to register text objects:
// From VimIndentObject.kt override fun init() { val api = api() api.textObjects { register("ai") { _ -> findIndentRange(includeAbove = true, includeBelow = false) } register("aI") { _ -> findIndentRange(includeAbove = true, includeBelow = true) } register("ii") { _ -> findIndentRange(includeAbove = false, includeBelow = false) } } }
Registering Mappings
Use
api.mappings { } to register mappings:
// From ParagraphMotion.kt override fun init() { val api = api() api.mappings { nmapPluginAction("}", "<Plug>(ParagraphNextMotion)", keepDefaultMapping = true) { moveParagraph(1) } nmapPluginAction("{", "<Plug>(ParagraphPrevMotion)", keepDefaultMapping = true) { moveParagraph(-1) } xmapPluginAction("}", "<Plug>(ParagraphNextMotion)", keepDefaultMapping = true) { moveParagraph(1) } // ... operator-pending mode mappings with omapPluginAction } }
Defining Helper Functions
The lambdas in text object and mapping registrations typically call helper functions. Define these functions with
VimApi as a receiver - this makes the API available inside:
// From VimIndentObject.kt private fun VimApi.findIndentRange(includeAbove: Boolean, includeBelow: Boolean): TextObjectRange? { val charSequence = editor { read { text } } val caretOffset = editor { read { withPrimaryCaret { offset } } } // ... implementation using API } // From ParagraphMotion.kt internal fun VimApi.moveParagraph(direction: Int) { val count = getVariable<Int>("v:count1") ?: 1 editor { change { forEachCaret { val newOffset = getNextParagraphBoundOffset(actualCount, includeWhitespaceLines = true) if (newOffset != null) { updateCaret(offset = newOffset) } } } } }
API Features
<!-- Fill in additional API features here -->How to Migrate Existing Extensions
What Stays the Same
- The extension still inherits VimExtensionFacade - this does not change
- The extension still registers in the XML file - this does not change
Migration Steps
Step 1: Ensure Test Coverage
Before starting migration, make sure tests exist for the extension:
- Tests should work and have good coverage
- If there aren't enough tests, create more tests first
- Verify tests pass on the existing version of the plugin
Step 2: Migrate in Small Steps
- Don't try to handle everything in one run
- Run tests on the plugin (just the single test class to speed up things) after making smaller changes
- This ensures consistency and makes it easier to identify issues
- Do a separate commit for each small sensible change or migration unless explicitly told not to
Step 3: Migrate Handlers One by One
If the extension has multiple handlers, migrate them one at a time rather than all at once.
Step 4: Handler Migration Process
For each handler, follow this approach:
-
Inject the API: Add
as the first line inside theval api = api()
functionexecute -
Extract to extension function: Extract the content of the execute function into a separate function outside the
class. The new function should:ExtensionHandler- Have
as a receiverVimApi - Use the api that was obtained before
- Keep the extraction as-is (no changes to logic yet)
- Have
-
Verify tests pass: Run tests to ensure the extraction didn't break anything
-
Migrate function content: Now start migrating the content of the extracted function to use the new API
-
Verify tests pass again: Run tests after each significant change
-
Update registration: Finally, change the registration of shortcuts from the existing approach to
where you call the newly created functionapi.mappings { }
Example Migration Flow
// BEFORE: Old style handler class MyHandler : ExtensionHandler { override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) { // ... implementation } } // STEP 1: Inject API class MyHandler : ExtensionHandler { override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) { val api = api() // ... implementation } } // STEP 2: Extract to extension function (as-is) class MyHandler : ExtensionHandler { override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) { val api = api() api.doMyAction(/* pass needed params */) } } private fun VimApi.doMyAction(/* params */) { // ... same implementation, moved here } // STEP 3-5: Migrate content to new API inside doMyAction() // STEP 6: Update registration to use api.mappings { } override fun init() { val api = api() api.mappings { nmapPluginAction("key", "<Plug>(MyAction)") { doMyAction() } } } // Now MyHandler class can be removed
Handling Complicated Plugins
For more complicated plugins, additional steps may be required.
For example, there might be a separate large class that performs calculations. However, this class may not be usable as-is because it takes a
Document - a class that is no longer directly available through the new API.
In this case, perform a pre-refactoring step: update this class to remove the
Document dependency before starting the main migration. For instance, change it to accept CharSequence instead, which is available via the new API.
Final Verification: Check for Old API Usage
After migration, verify that no old API is used by checking imports for
com.maddyhome.
Allowed imports (these are still required):
com.maddyhome.idea.vim.extension.VimExtensioncom.maddyhome.idea.vim.extension.api
Any other
com.maddyhome imports indicate incomplete migration.