Fixes wrong recorded inputs when code completion introduces an import.
Fixes wrong recorded inputs when completing a static member with a partial type name. Example: `WiE.A` -> `WindowEvent.ACTION_EVENT_MASK`
Implements five new vimscript list functions:
- count(): counts occurrences of a value in a list/dict
- index(): finds first index of a value in a list
- min()/max(): finds minimum/maximum value in a list/dict
- range(): generates a list of numbers with optional stride
Includes error handling for edge cases like zero stride and invalid ranges.
Implement five commonly used vimscript functions:
- repeat(expr, count): Repeats strings or lists multiple times
- char2nr(char): Converts character to Unicode code point
- nr2char(number): Converts code point to character
- trim(text, [mask], [dir]): Trims whitespace or custom characters
- reverse(object): Reverses lists in-place or returns reversed string
All functions include comprehensive test coverage and follow vim's
official behavior including edge cases.
Now, the inputProcessor will be called after all closing is finished. This includes the mode that won't be CMD_LINE anymore, but the one that was used before entering CMD_LINE
Surprisingly Float is converted to String, and then concatenated. But this is only supported for the binary concatenation operator, not the compound assignment concatenation operator. This lead to improved validation and behaviour closer to Vim.
The code doesn't require nullable arguments, which removes null check error messages. Also removed some "not implemented" messages from resources. There's no need for them to be in resources, hopefully we'll implement them soon.
Prefer `toVimString` to `asString`. It makes it more explicit that the caller requires a Vim String type, and must follow Vim's conversion rules, rather than simple requiring a string. It also prevents confusion with other string functions such as `toString`, `toOutputString`, `toInsertableString`.
The function is deprecated because it's in use by external plugins. There is no change in functionality.
The `asBoolean` method does not make it clear that only a Vim Number can be treated as a boolean and incorrectly allowed a Float to be converted.
If a caller needs a boolean value, it should be explicit that it must get a Number by calling `toVimNumber` and then using the new `booleanValue` accessor. This correctly allows conversion from a String, and other Vim datatypes will correctly raise an error.
The implementation of `VimString.toVimNumber` has been updated to match Vim behaviour, and the behaviour of the previously called `VimString.asDouble` by allowing trailing characters while converting a string to a number.
The `asBoolean` function is deprecated because it is used by external plugins.
It's confusing to know the rules for converting between data types. This change introduces a semantically named method for getting a string representation that can be used in output and error messages.
- Emphasize using git show to see full commit content
- Note that test files contain good examples for changelog
- Encourage looking at actual code changes for better understanding
- Help find specific command examples from tests
- Emphasize reading PR comments from previous changelog updates
- Add gh command to view PR comments
- Note that review feedback may indicate what to avoid
- Help ensure special instructions from PR discussions are followed
- Clarify that CHANGES.md is a large file
- Instruct to focus on [To Be Released] section and recent versions
- Avoid reading the entire file unnecessarily
- Create separate section with detailed instructions for changeNotes
- Clarify that content should match CHANGES.md exactly (no summarizing)
- Provide HTML formatting guidelines and examples
- Emphasize not to create shorter or condensed versions
- Include example showing Markdown to HTML conversion
- Specify that PR descriptions should be taken directly from PR titles
- Don't regenerate or rewrite PR descriptions
- Maintain author's original wording for PR entries
- Clarify that PRs have different inclusion criteria than Features/Fixes
- Note that internal changes and refactoring PRs should be included
- Emphasize acknowledging community contributions regardless of visibility
- Make clear that "user-visible only" rule doesn't apply to PRs section
- Add "Fixed inlay offset calculations" as bad example
- Show better alternative with specific user-visible case
- Reinforce that technical internal fixes should either have clear user impact or be omitted
- Include bad example: "Fixed count validation in text objects" (too vague)
- Show better alternative: specific command that was fixed
- Emphasize: if unable to determine specific case, omit entry rather than be unclear
- Help ensure all changelog entries are meaningful to users
- Clarify that each change should appear in only ONE subsection
- Add note that features can also use YouTrack link format
- Emphasize no duplication between Features, Fixes, and other categories
- Update both Format Notes and Writing Style sections for clarity
- Change PR title format to "Update changelog: <super short summary>"
- Add example showing how to summarize key changes in title
- Update both GitHub workflow and changelog instructions for consistency
- Guide to search for and include official documentation links
- Add links to JetBrains features and Vim commands when relevant
- Include examples showing linked IntelliJ features (Next Edit Suggestion, multiple cursors)
- Help users find more information about mentioned features
- Add instruction to include examples of commands/operations in changelog entries
- Update example entries to demonstrate including specific commands
- Show how to describe what now works for both fixes and features
- Help users understand how to use new features or what was fixed
- Clarify that internal code changes should not be logged
- Emphasize that changelog is for users, not developers
- Strengthen user-focused writing style guidelines
- Exclude refactoring, code cleanup, and internal architecture changes
- Simplified CLAUDE.md to avoid redundancy with other docs
- Created .claude/changelog-instructions.md with detailed changelog maintenance guide
- Updated GitHub Action to reference the new changelog instructions doc
- Added instructions for updating changeNotes in build.gradle.kts
- Added notes about excluding API module changes (experimental status)
- Added step to check previous changelog PRs for special instructions
The append, prepend, and remove functions now correctly handle empty
option values. When an option is empty (""), we use an empty list
instead of splitting the empty string which would result in [""].
This fixes test failures in OptionScopeTest for operations on empty options.
This workflow runs daily at 5 AM UTC (or manually) and uses Claude to:
- Review commits and update CHANGES.md with meaningful changes
- Maintain the exact format used before the changelog gap
- Include YouTrack links for fixes (format: [VIM-XXXX](url))
- Reference merged PRs (excluding dependabot and Claude PRs)
- Handle the historical gap between versions 2.9.0 and 2.28.0
- Create PRs only when there are actual changes to document
Claude has access to git, GitHub CLI, and can fetch from YouTrack,
GitHub, and JetBrains plugin pages to gather accurate information.
This workflow runs three times a year (August 15, April 30, December 1)
and uses Claude to check if new IntelliJ versions need to be added to
TeamCity test configurations. Claude will automatically create a PR if
a new version is needed.
The workflow can also be triggered manually for testing.
Added prominent warning notice to all Plugin API documentation files:
- Plugin-API-reference.md
- Plugin-API-introduction.md
- Plugin-API-quick-start-guide.md
- Plugin-API-tutorial-replace-with-register.md
The warning clearly states that the API is experimental and not
recommended for production use, with potential breaking changes.
- Document that option() function now returns a value
- Add comprehensive documentation for list option methods (append, prepend, remove)
- Add utility methods documentation (toggle, split)
- Include practical usage examples and common use cases
- Update method signatures to reflect non-nullable returns
- Add Vim command equivalents for better understanding
This allows users to easily retrieve values from option scope:
val x = option { get<Int>("history") }
- Changed option() signature from Unit to generic T return type
- Updated VimApiImpl implementation to return the lambda result
- Added test demonstrating the new return value capability
The implementation always returns a value or throws an exception,
so the return type should be non-nullable to reflect this behavior.
Updated extension functions to remove unnecessary null checks.
- Convert OptionScope from abstract class to interface
- Extract inline functions with reified types as extension functions
- Make getOptionValue() and setOption() public interface methods
- Remove internal modifier layer functions
- Update OptionScopeImpl to implement new interface
- Add documentation recommending extension function usage
- Update test imports to use new extension functions
- append(): adds values to end of comma-separated list (like Vim's +=)
- prepend(): adds values to beginning of list (like Vim's ^=)
- remove(): removes values from list (like Vim's -=)
- All functions prevent duplicate values from being added
- Comprehensive test coverage for all scenarios
Adds a simple toggle() function that flips boolean option values.
Works with both full option names and abbreviations.
Example usage:
toggle("ignorecase") // true → false, false → true
toggle("ic") // works with abbreviations
Adds a concise String.split() extension function within OptionScope that splits
comma-separated option values into lists. This simplifies working with list-type
options like 'virtualedit', 'whichwrap', etc.
Example usage:
val values = get<String>("virtualedit")?.split() ?: emptyList()
// "block,all" → ["block", "all"]
Added extensive test coverage for the OptionScope API including:
- String list options with various formats (single, multiple, empty values)
- Error handling for invalid values, empty names, and type mismatches
- Boundary conditions for integer options
- Boolean/integer type conversions
- Global vs local option scoping
- Option abbreviations
- Edge cases like trailing/leading commas and very long strings
This is necessary because I would like to use `registerMappings` in
the constructor of `NerdDispatcher` where we have no access to
private methods of `NerdTree`.
Also, `callAction` is moved into `nerdtree.Mappings.Action` and
duplicated `addCommand` is removed.
The user may have pressed `g` accidentally and wish to perform an
operation which is not prefix by `g`.
This gives the user a way to clear the keys entered previously and
matches Vim's behavior.
What we can benefit from this approach:
- User perspective
The SpeedSearch input will pop up immediately to indicate that
`/` has been pressed, and search text can then be entered to
filter the tree nodes.
- Codebase perspective
The `waitForSearch` property can be removed from the Dispatcher
objects, and we can get rid of `ToolWindowManagerListener` and
the concurrency issue in it. This keeps code simple and readable.
In my previous attempt to preserve the `waitForSearch` prop, the
Dispatcher object had to be passed to each action impl as an
argument.
1) ExException is wrapped with the IllegalArgumentException. We cannot use the raw ExException as it's not defined in the API module. So, if any client will have an access only to the API module, they won't be able to handle this kind of the exception.
2) getOption throws IllegalArgumentException if the type classifier is null. I don't know when this might happen, but this looks more like a plugin developer error. Also, this allows to distinguish the wrong option name vs this type of problem
3) In setOption and getOption throw an exception if there is no such option. This clearly explains what is wrong with this call.
Note: there was a question of using exceptions vs some return values, even like `Result`. The decision falls into the using of exceptions as using the wrong arguments in the plugin is a programming error from the user point of view.
This means, if there is a plugin that checks some option that in reality doesn't exist, it's not a fail of the end user and the end user cannot do anything about that.
Also, using the `Result` will force the plugin developer to handle all possible failure branches, what is quite unnecessary when accessing a well-defined option.
Previously, we would drop out of VimShortcutKeyAction when hitting Tab in Insert mode. This allowed Emmet to work because ExpandLiveTemplateByTabAction (one of the many actions registered for Tab) would have a chance to handle it.
Now we let Tab actions try to handle the key before Vim does, so we can let Vim handle Tab. In Insert mode, Vim now inserts the tab (or equivalent spaces) by invoking the "EditorTab" action, which is the same as the TabAction handler in the list of actions. Because Vim does this, we can now easily repeat inserting Tab without workarounds, remap `<Tab>` and `<S-Tab>` and Vim will update scroll locations after inserting the text.
Fixes VIM-2331. FixesJetBrains/ideavim#938. FixesJetBrains/ideavim#280
Would previously normalise against the entire buffer line length rather than just the current visual line length. For short lines, this would not include inlays, and would therefore position the caret wrong when moving up/down at end of line. For long, wrapped lines, this just plain wouldn't work.
Fixes VIM-3997
The way we split plugin.xml was outdated. Also, it started to show errors in highlighting, even there were no errors.
It's better to keep everything in a single file
This function is not provided by vim and by using it from the plugins we may drop the digraphs created by other plugins.
It'd be better to provide a just remove function. However, let's skip it for now. Also, maybe we should use an approach with the owners
The suspending operations must not be performed under the read or write actions as this will lead to performance issues or freezes.
Also, the current implementation of launching coroutine under the write action is simply incorrect. The coroutine will escape the write action into another thread.
The current representation of Mode with `returnTo` is quite complicated and we're not even sure it'll remain like that.
At the same time, `mode()` function in Vim has quite a reach specification and there is a small chance it'll be changed. With this approach, we use values from Vim, yet in a form of enum.
`ReadImpl` didn't use anything from `VimScopeImpl`. This would make sense if `Read` inherited `VimScope`, but it doesn't.
Also, we'll always be able to bring back the dependency if we figure out it's necessary.
Removing `project` parameter from NerdAction.Code.action makes it
possible to extend NERDTree support to all tree components other
than the Project tool window.
- We want to be able to execute functions defined on read scope under write lock as well, which means we want to have transaction extend read. However, due to conflicting names for caret scope builders (forEachCaret, withPrimaryCaret etc.) it was necessary to split it into two scopes:
1) Read - contains only functions available under read lock
2) ReadScope - contains both caret scope builders and functions defined on Read
- In the `api` module:
- remove dependency on `vim-engine` module due to circular dependencies
- move implementations of scopes to the `vim-engine`
- add VimPluginDsl annotation to interfaces
- make VimScope abstract class
- remove ScopeBuilders file and move scope builders to the VimScope abstract class
- In the `vim-engine` module:
- add dependency on `api` module
- add implementation of scopes
- in VimInjector add new field - pluginService (reason for that is because functions from VimExtensionFacade are not available in the VimEngine)
The prompt character is now implemented as a custom view, only in the UI, and not inserted directly into the text. This simplifies management of the text (and removes/fixes an exception due to manually handling prompt offset), and also allows highlighting of the prompt character.
We don't need a secondary UI element hierarchy without shortcuts because the shortcuts are no longer handled by the UI, but by the key handler. This secondary instance was used by modal input, and this is also managed by the key handler, consuming key strokes first if a modal input prompt is active
Instead of replacing the whole string, which will reset scroll position, delete or insert the required text/offsets, and let the text field manage scroll position
All key handling is done with the Vim pipeline, so make sure we don't have any Swing key bindings registered. This is quite confusing, so document what's going on
All CMD_LINE actions are registered the same as other Vim commands, which means all shortcuts are already registered with the IDE's action system. The existing VimShortcutKeyAction class will dispatch shortcuts to the key handler, where they will be handled as real CMD_LINE actions.
For visual selections spanning multiple lines, keep caret position
if it's on the first line. Otherwise move the caret to the start of
the first selected line.
Note that this implementation assumes that the 'gU' / 'gu' command in
visual mode is equivalent to 'U' / 'u'. While 'v_gU' and 'v_gu' are not
explicitly documented in Vim help, we treat these commands as identical
based on observed behavior, without examining Vim's source code.
Because of some changes, if we define the test using `testIde` registering, they're not executed properly and don't work.
Now, we don't exclude these tests from the main test execution, so they have to be excluded explicitly
This is a part of work for VIM-3935. Since we'll have to change the `CommandProcessor` execution, this may affect the "undo" command behavior. To ensure the stability, we add undo tests before making a refactoring.
This is a part of work for VIM-3935. Since we'll have to change the `CommandProcessor` execution, this may affect the "undo" command behavior. To ensure the stability, we add undo tests before making a refactoring.
This is a part of work for VIM-3935. Since we'll have to change the `CommandProcessor` execution, this may affect the "undo" command behavior. To ensure the stability, we add undo tests before making a refactoring.
IJ platform runs additional project leak checks when it detects teamcity run. It was quite complicated to understand why tests were failing on TC, but not locally, so I decided to enable TeamCity emulation to have these checks locally.
However, it turned out that in addition to that, an IJ platform also collects CPU statistics on TeamCity, which may take around a minute. This dramatically affects the performance of local execution. So, this property is turned off.
This requirement was a fix for VIM-318 introduced in ac654d70fa.
However, now we always run IdeaVim on EDT. Also, this check prevents the migration of IdeaVim to the background thread: VIM-3935
This change should fix issues in Rider evaluate window and other places.
However, we may face small issues as IdeaVim will be disabled in more places than it used to be. However, this approach looks safer as before that we were disabling some random keys
If an alt+enter intention is invoked from Search Everywhere, IdeaVim's Track Action ID shows "Cannot detect action ID" instead of explaining that there is no action ID.
Relates to VIM-2541
It turned out the editor.getComponent returns not the editorComponent, but the parent of the component. This caused no problems until the AI plugin started to register enter/esc on the editor component directly. Since an editor component is more specific than the component with vim actions, the vim shortcuts were suppressed.
In this change, we start to register shortcuts on the editor component directly, allowing them to properly work on the same level as AI shortcuts. This is also the level where the shortcuts are supposed to be registered.
While it might not make sense to replay an incorrect `<Plug>` or `<Action>` mapping as characters where they are likely to cause unexpected behaviour as Normal commands, this is standard Vim behaviour (at least for `<Plug>`).
Note that `<Plug>` (and `<Action>`) is a special key code that cannot be typed. In Insert mode, Vim expands it to the text "<Plug>".
All commands are called in Normal, so there is no need to check mode at execution time. The SAVE_VISUAL flag is perhaps poorly named, as it still change to Normal mode, but will save the current selection for commands that need (usually because they interact with IDE features). The `:normal` command does not need the current selection.
Rider's dotCover indicator is marked as "last" rather than relative to other dotnet icons. This position pushes out IdeaVim widgets, so make sure we're after "dotCoverIndicator"
See also DCVR-13021
I used the code in VimChangeGroupBase.getDeleteRangeAndType to do the check.
For an example of what this fixes, with the document:
```
${c}Hello!
```
executing "y}llp" before this change yields
```
HelHello!
lo!
```
but doing it with this change yields
```
Hello!
Hello!
````
There are several problems with it:
- It was highly unstable, so it was constantly disabled
- Qodana baseline file is huge, it's almost the size of the whole repo
Historically, IdeaVim was starting read or write action based on command type. However, this is incorrect because of several reasons:
- The command of different type may perform a write/read action
- The write lock with a lot of code inside does cause application freezes
- Since a large branching of code happens inside the write action, there is a chance of read action request, resulting in the deadlock
Also, now this prevents from implementing a proper execution of VIM-3376.
It's not clear why the write action was needed. Probably, it was a mistake and it supposed to be a check for EDT. However, since the check for EDT exists right in the constructor of the potemkin progress, let's remove it at all.
The `ge` motion incorrectly had `stopAtEmptyLine` set to false, but `ge` always stops at empty lines. This option is only applicable for forward motion, so is ignored by the implementation of `findPreviousWordEndOne`.
Adds extra tests to confirm that deleting previous word in Insert mode was already working as expected (relates to VIM-1650)
Also fixes an edge case for a test that was marked as behaving differently to Vim, but was actually showing buggy behaviour due to its unorthodox caret placement. It looked like the caret was placed at the end of the line, but it was actually placed _passed_ the last character in the file. Adding extra text below the caret placement would cause the action to behave as expected, and the test would then match Vim behaviour. However, it is possible to get this caret position in the editor and in Vim, with `virtualedit=onemore`, and the IdeaVim implementation was wrong. The test has been updated to provide the correct scenario, and the implementation has been fixed.
This is needed for the new terminal. Before this change, it was impossible to put the caret at the line end, even taking the fact the IdeaVim is disabled in the new terminal.
Simplify and reorganize logic in isEnabled() for improved readability and maintainability. Adjust logging messages for consistency and replace unused exception variable with underscore.
As the GitHub Copilot is placed right in the editor component, the IdeaVim's actions are also collected. Action promoter promotes the IdeaVim's action and, since the data context has the editor, executes a Vim's actions like backspace.
Now we make sure that IdeaVim works only in the actual editor.
Since this change may affect some places where the IdeaVim used to work, but won't work now (this will be a surprise), a registry key is introduced to help the users that face this problem.
The `editorInFocus` used the weak reference in order to avoid editor leaks. However, if the user is unlucky, the GC may be called in between the console closing and switching focus to the new editor. In this case, the logic breaks and the Editor remains in the insert mode.
Now, we store only the required information about the last used editor.
This commit fixes 2 bugs at once:
1. Correctly trim surround with closing brackets motion, e.g.: `cs()`.
It should trim all surrounding white space or leave things unchanged if
there's no space.
For example cases like these:
"( ${c}foo )" // single spaces
"( ${c}foo )" // multiple spaces
"( ${c}foo)" // assymetric spaces
"(${c} )" // single space without text
"(${c} )" // multiple spaces without text
Should trim white spaces into these results accordingly:
"${c}(foo)"
"${c}(foo)"
"${c}(foo)"
"${c}()"
"${c}()"
In case of no spaces - they should be left unchanged, e.g.:
"(${c}foo)" // no spaces around the word
"(${c})" // empty parenthesis
2. Leave empty parenthesis unchanged. IdeaVim now deleted them instead.
**Context**:
In vim surround extendsion closing brackets (`}, ], )`) should remove
whitespace when using `cs` movement.
Example:
- Before: `{ example }`
- Movement: `cs{}`
- After: `{example}`
This was because were replaced with a string from `SURROUND_PAIRS` map,
which does not have any context about removing characters.
**Solution**:
Inspired from VSCode's VIM plugin[^1], I have introduced new class
`SurroundPair` that will carry this context about need to trim
characters.
**Disclaimer**:
I have never written in `Kotlin` so solution may be not use best
practices, though at least this PR seems to fix the problem and tests
are passing.
**Ticket**:
- https://youtrack.jetbrains.com/issue/VIM-1824/Vim-Surround-Does-not-remove-whitespace-with-the-closing-bracket
[^1]: 04fe42aa81/src/actions/plugins/surround.ts (L455)
We need this for undo subsystem. Mode change is not something that we want to be a separate entity in the undo log
P.S. It's not a full list of such commands, e.g. <ESC> for leaving insert mode is missing. It is because <ESC> may insert some text after visual block mode, I'll figure out a solution for this later
1. `rawText` vs `text` is confusing and `rawText` was misused, we do not need this field in IdeaVim at all
2. `rawText` and `keys` are the same thing, just parsed. And for some reason, rawText was a nullable field
3. Register is an immutable class now
4. Working with Register class is easier and more compact now
When I added the selection clipboard, it was confusing to explain to people how this option works and why they should prepend, because the parsing was broken
I've also removed "exclude" part, because it doesn't work in IdeaVim and can confuse people
This is important for initialising options. We can't rely on FileEditorManager.selectedTextEditor, as 2024.2 changed behaviour to reset to null during creation of a new editor. This fixes tests (and option initialisation) for 242.
Also supports `mapclear!` and `unmap!`
Moves parsing of the bang modifier to the parser so we can tell the difference between `map! foo bar` and `map ! foo bar`
E.g. `map foo bar` and `vmap foo baz` would only output one map for `foo` when calling `:map`. Now it will output all maps that match the ex command's modes. This change also improves the marker characters in the first column of map output.
This was needed when action keys were registered in a comma separated list in a single XML attribute string. Additional encoding was needed for angle brackets and commas. Registration has changed, and this is no longer needed
The window splitting was refactored to initialize the editor async. So, we have to wait till the editor will become available.
This can't be done while holding the EDT, so we start this bunch of tests from the non-EDT thread.
The problem happens in tests: after the refactorings in 242, the `EditorListenerTracker` may be called before the initialization of the IdeaVim. In this case, it'll report the VimDocumentListener as a leaked listener, however, it's incorrect.
Generally, I think that this situation with the listener tracker is a bug.
There should be no changes in IdeaVim behaviour as the multicaster does the same thing: subscribes every editor on this listener. However, the multicaster does this in the "registerEditor" stage. However, I don't think this is a problem.
The problem happens in tests: after the refactorings in 242, the `EditorListenerTracker` may be called before the initialization of the IdeaVim. In this case, it'll report the FocusChangeListener as a leaked listener, however, it's incorrect.
Generally, I think that this situation with the listener tracker is a bug.
There should be no changes in IdeaVim behaviour as the multicaster does the same thing: subscribes every editor on this listener. However, the multicaster does this in the "registerEditor" stage. However, I don't think this is a problem.
With the AI functionality, the shortcut ctrl-right got more important. However, previously it was defined as VIM_ONLY shortcut. However, taking the fact that IJ defines several actions for such shortcuts, it's not clear why we prohibit the users from using these shortcuts with the IDE actions.
Taking the fact, that we default shortcuts to VIM, I expect no changes in the behaviour.
However, just arrows are still hidden from setting the IDE handler. I think, it reduces the cognitive load for the user, especially taking the fact that arrows work almost equally in vim and IJ.
List is based on Vim's documentation, although not all digraphs are documented. Additional digraphs are added based on the output of `:digraphs`. These additional digraphs include some digraphs that produce the same character, so the code is updated to handle duplicates, with the same ordering/priority as Vim.
Extra digraphs include the Euro symbol (`=e` and `Eu`), quadruple prime (`4'`) and bullet (`oo`), amongst others.
Also removes a number of non-standard digraphs. The symbols generated don't match the descriptions. The code appears to be private use, so are not reliable. Once custom digraphs are implemented, they can be easily added back in `~/.ideavimrc`
Vim only use the `~` prefix if the encoding is "latin1". We can just treat it as though the encoding is Unicode, and match the other places we format printable characters. Note that for Vim, if `'display'` contains "uhex", then all unprintable characters are shown in hex, including control characters (`^C`, etc.). IdeaVim does not support the `'display'` option.
`OperatorArguments.mode` is the mode *before* the command was completed, rather than the current mode, which is non-obvious. E.g. for a command in Insert mode, it will still be Insert, and for a (simple) command in Normal, it will still be Normal. The only difference is that a command such as `dw` would be in Operator-pending before the command is completed. That logic is not required for this method, so it's safe to use the current mode.
This allows us to start to deprecate `OperatorArguments.mode`.
`OperatorArguments.mode` is the mode *before* the command is completed, so might be Visual, Operator-pending, Insert, etc. It's not immediately obvious this is the case, so we're going to deprecate `OperatorArguments.mode` to avoid confusion with `editor.mode`.
It's not required for this method because it's only called for Visual-block mode.
Register specific handlers for Operator-pending mode instead of relying on a runtime flag for behaviour. Also refactors and renames some arrow motion handlers.
It's easier to just look at mode. We don't need the additional check on command builder, because we can't be in OP_PENDING without pushing an operator action to the command builder
Only Command has a count. The motion argument is now a sealed class hierarchy, and consists only of the motion action and optional argument. This is to reduce confusion over which count to use, and potential incorrect calculation of the count
Unlike other IDEs, Rider has multiple client sessions. The IDE itself is the "local" session, while the external ReSharper process is treated as a "frontend" process. The code to get local editors was erroneously getting `ALL` sessions, rather than just `LOCAL` sessions, and assuming that the first was the local session. In Rider, opening three instances would add three additional clients, and that would change the order.
I don't know why I changed `LOCAL` to `ALL` when previously changing this bit of code. AIUI, using `LOCAL` should work fine. If it turns out that CWM or remote dev require `ALL`, please document why.
Fixes VIM-3549
Steps to reproduce:
1. Select some text inside diff editor
2. Open 'Find in Files' window and search for file
3. Open the found file
Result: current mode is VISUAL
Some of the text input fields where Vim should not work at all had block carets.
It did not happen before, because previously we had a unique VimStateMachine for each editor and for newly created editors it was in INSERT mode. And we did call the updateSecondaryCaretsVisualAttributes method for editors that have nothing to do with Vim, but because of the INSERT mode it was looking OK.
However, now the VimStateMachine is global, and we can't rely on local INSERT anymore.
This commit forbids updating caret visual attributes for editors that do not support Vim.
NOTE: `isIdeaVimDisabledHere` is broken during editor creation handling, it always returns true. However, we do not trigger carets redraws on editor creation and do it on focus events, so it should work.
We no longer need to track a previous fallback argument type, since we don't support nested commands inside a command builder. We can just return the current argument type, or its fallback
This was to handle nested commands, e.g. inserting a digraph inside a search `d/foo<C-K>OK<CR>`. The command line now has its own command builder, so this check is no longer needed
The last command part is not guaranteed to be a "select register" part. The user might have selected a register then typed an operator, and we might be waiting for a motion.
Originally this is needed to update the dependency on AceJump, which uses the kotlin 2.0 compiler, and its classes are not compatible with the old compiler
I'm not sure what causes the issue, but everything was working when we were updating visual attributes per each caret and... let update them per each caret
Typing is more suitable for command lines than a modal input, and most likely it should be used instead
It is still possible to support typing by properly implementing the handleKey method
We do not need the `doAll` condition (because the next code block with `doAll` adjusts line and column for the next match to be correct),
And the line2 can't be >= editor.lineCount()
shouldRecord value was only updated in ModeInputConsumer when the key was not handled. But when the key is not handled, it is not passed to finishedCommandPreparation and the shouldRecord value is not used
We may create a command line via the VimCommandLineService and forget (or do not know) about calling startExEntry necessary. So we move its logic inside the creation of the command line
Initially, injector was initialized in VimPlugin, assuming that almost every interaction with the plugin goes through it. However, with the plugin evolution, this class starts to be less used.
As IJ doesn't have any single entry point for the plugins, we initialize it in multiple places.
However, the architecture where the plugin might be not initizlied is considered as a bad acrhitecture and should be reviewed.
Related ticket: VIM-3369
With gradle plugin 2.0 this cache fails on GitHub Actions. This solution is temporlly and generally the `integrationsTest` should be migrated from `doLast` to some other task approach
Why is the old interface bad?
- it is not obvious. You cannot create a new panel or check if it is already created. Only "getOrCreate" it
- output panel is bound to editor while in Vim it is global
- we have the `isActive` field and the `clear()` method at the same time, because interface implies that you store the same instance of the panel and reactivate it for each output and I don't like it. We also can forget to call `clear()` after reusing panel
- we cannot "build" output before showing to make the panel more efficient. With multiple carets we can only cal `output(oldText + newText)` for each caret, and it is bad. (imagine we have global command with a lot of matches and for each time we will need to call the `output(oldText + newText)`)
- the `output()` method shows panel, activates it and updates it
- there are more things that I do not like, but the points above should be already enough
This bug was caused by two reasons:
1. KeyHandler state is not longer per-editor and we can't reset it on editor creation
2. We do not need to do such things on editor creation. What actually matters is focusing the editor
- Performance optimizations (unless they fix a noticeable user issue)
- Remember: The changelog is for users, not developers
## Writing Style
- **Be concise**: One line per change when possible
- **User-focused**: Describe what changed from user's perspective
- Write for end users, not developers
- Focus on visible behavior changes, new commands, fixed issues users experience
- Avoid technical implementation details
- **Include examples** when helpful:
- For fixes: Show the command/operation that now works correctly
- For features: Demonstrate the new commands or functionality
- Good example: "Fixed `ci"` command in empty strings" or "Added support for `gn` text object"
- Bad examples (too vague, unclear what was broken):
- "Fixed count validation in text objects"
- "Fixed inlay offset calculations"
- Better: Specify the actual case - "Fixed `3daw` deleting wrong number of words" or "Fixed cursor position with inlay hints in `f` motion"
- **If you can't determine the specific case from tests/code, omit the entry rather than leave it unclear**
- **Add helpful links** for context:
- When mentioning IntelliJ features, search for official JetBrains documentation or blog posts
- When referencing Vim commands, link to Vim documentation if helpful
- Example: "Added support for [Next Edit Suggestion](https://blog.jetbrains.com/ai/2025/08/introducing-next-edit-suggestions-in-jetbrains-ai-assistant/)"
- Use web search to find the most relevant official sources
- **Include references**: Add [VIM-XXXX] for YouTrack tickets, (#XXX) for PRs
- **No duplication**: Each change appears in exactly ONE subsection - don't repeat items across categories
- **Use consistent tense**: Past tense for completed work
## Examples of Good Entries
```
### Features:
* Added support for `gn` text object - select next match with `gn`, change with `cgn`
* Implemented `:tabmove` command - use `:tabmove +1` or `:tabmove -1` to reorder tabs
* Support for `z=` to show spelling suggestions
* Added integration with [Next Edit Suggestion](https://blog.jetbrains.com/ai/2025/08/introducing-next-edit-suggestions-in-jetbrains-ai-assistant/) feature
* Support for [multiple cursors](https://www.jetbrains.com/help/idea/multicursor.html) in visual mode
### Fixes:
* [VIM-3456](https://youtrack.jetbrains.com/issue/VIM-3456) Fixed cursor position after undo in visual mode
* [VIM-3458](https://youtrack.jetbrains.com/issue/VIM-3458) Fixed `ci"` command now works correctly in empty strings
* [VIM-3260](https://youtrack.jetbrains.com/issue/VIM-3260) Fixed `G` command at file end with count
* [VIM-3180](https://youtrack.jetbrains.com/issue/VIM-3180) Fixed `vib` and `viB` selection in nested blocks
### Merged PRs:
* [805](https://github.com/JetBrains/ideavim/pull/805) by [chylex](https://github.com/chylex): VIM-3238 Fix recording a macro that replays another macro
```
## IMPORTANT Format Notes
### For Fixes:
Always put the ticket link FIRST, then the description:
```
* [VIM-XXXX](https://youtrack.jetbrains.com/issue/VIM-XXXX) Description of what was fixed
```
### For Features:
- Without ticket: Just the description
- With ticket: Can use either format:
- Description with pipe: `* Feature description | [VIM-XXXX](https://youtrack.jetbrains.com/issue/VIM-XXXX)`
- Link first (like fixes): `* [VIM-XXXX](https://youtrack.jetbrains.com/issue/VIM-XXXX) Feature description`
### Avoid Duplication:
- **Each change should appear in only ONE subsection**
- If a feature is listed in Features, don't repeat it in Fixes
- If a bug fix is in Fixes, don't list it again elsewhere
- Choose the most appropriate category for each change
### Merged PRs Special Rules:
- **Different criteria than other sections**: The exclusion rules for Features/Fixes don't apply here
- **Include PRs from external contributors** even if they're internal changes or refactoring
- **List significant community contributions** regardless of whether they're user-visible
- **Format**: PR number, author, and brief description
- **Use PR title as-is**: Take the description directly from the PR title, don't regenerate or rewrite it
- **Purpose**: Acknowledge community contributions and provide PR tracking
- The "user-visible only" rule does NOT apply to this section
## Process
1. Read the current CHANGES.md (only the top portion - focus on `[To Be Released]` and recent versions)
2. Check previous changelog PRs from GitHub:
- Review the last few changelog update PRs (use `gh pr list --search "Update changelog" --state all --limit 5`)
- **Read the PR comments**: Use `gh pr view <PR_NUMBER> --comments` to check for specific instructions
- Look for any comments or instructions about what NOT to log this time
- Previous PRs may contain specific exclusions or special handling instructions
- Pay attention to review feedback that might indicate what to avoid in future updates
3. Check git tags for any undocumented releases
4. Review commits and PRs since last entry
5. Group changes by release or under [To Be Released]
6. Update CHANGES.md maintaining existing format
7. Update the `changeNotes` section in `build.gradle.kts` (see detailed instructions below)
8. Create a PR only if there are changes to document:
- Title format: "Update changelog: <supershortsummary>"
- Example: "Update changelog: Add gn text object, fix visual mode issues"
- Body: Brief summary of what was added
## Updating changeNotes in build.gradle.kts
The `changeNotes` section in `build.gradle.kts` displays on the JetBrains Marketplace plugin page. Follow these rules:
### Content Requirements
- **Match CHANGES.md exactly**: Use the same content from the `[To Be Released]` section
- **Don't create a shorter version**: Include all entries as they appear in CHANGES.md
- **Keep the same level of detail**: Don't summarize or condense
### HTML Formatting
Convert Markdown to HTML format:
- Headers: `### Features:` → `<b>Features:</b>`
- Line breaks: Use `<br>` between items
- Links: Convert markdown links to HTML `<a href="">` tags
- Bullet points: Use `•` or keep `*` with proper spacing
- Code blocks: Use `<code>` tags for commands like `<code>gn</code>`
### Special Notes
- **IMPORTANT**: Keep any existing information about the reward program in changeNotes
- This content appears in the plugin description on JetBrains Marketplace
### Example Conversion
Markdown in CHANGES.md:
```
### Features:
* Added support for `gn` text object
* [VIM-3456](https://youtrack.jetbrains.com/issue/VIM-3456) Fixed cursor position
```
HTML in changeNotes:
```html
<b>Features:</b><br>
• Added support for <code>gn</code> text object<br>
IdeaVim is an open source project created by 80+ contributors. Would you like to make it even better? That’s wonderful!
IdeaVim is an open source project created by 130+ contributors. Would you like to make it even better? That’s wonderful!
This page is created to help you start contributing. And who knows, maybe in a few days this project will be brighter than ever!
:warning: The plugin is currently under a huge refactoring aiming to split into vim-engine and IdeaVim in order to
support the new [Fleet IDE](https://www.jetbrains.com/fleet/). Please see [Fleet refactoring](#Fleet-refactoring).
# Awards for Quality Contributions
In February 2025, we’re starting a program to award one-year All Products Pack subscriptions to the implementers of quality contributions to the IdeaVim project. The program will continue for all of 2025 and may be prolonged.
Subscriptions can be awarded for merged pull requests that meet the following requirements:
- The change should be non-trivial, though there might be exceptions — for example, where a trivial fix requires a complicated investigation.
- The change should fully implement a feature or fix the root cause of a bug. Workarounds or hacks are not accepted.
- If applicable, the change should be properly covered with unit tests.
- The work should be performed by the contributor, though the IdeaVim team is happy to review it and give feedback.
- The change should fix an issue or implement a feature filed by another user. If you want to file an issue and provide a solution to it, your request for a license should be explicitly discussed with the IdeaVim team in the ticket comments.
We'd like to make sure this award program is helpful and fair. Since we just started it and still fine-tuning the details, the final say on giving licenses remains with the IdeaVim team and the requirements might evolve over time.
Also, a few notes:
- If you have any doubts about whether your change or fix is eligible for the award, get in touch with us in the comments on YouTrack or in any other way.
- Please mention this program in the pull request text. This is not an absolute requirement, but it will help ensure we know you would like to be considered for an award, but this is not required.
- During 2025, a single person may only receive a single subscription. Even if you make multiple contributions, you will not be eligible for multiple awards.
- Any delays caused by the IdeaVim team will not affect eligibility for an award if the other requirements are met.
- Draft pull requests will not be reviewed unless explicitly requested.
- Tickets with the [ideavim-bounty](https://youtrack.jetbrains.com/issues?q=tag:%20%7BIdeaVim-bounty%7D) tag are good candidates for this award.
## Before you begin
- The project is written in Kotlin and Java. Choose whichever language you feel more comfortable with,
or maybe one that you’d like to get to know better (why not start [learning Kotlin](https://kotlinlang.org/docs/tutorials/) right now?).
- The project is primarily written in Kotlin with a few Java files. When contributing to the project, use Kotlin unless
you’re working in areas where Java is explicitly used.
- If you come across some IntelliJ Platform code, these links may prove helpful:
* [IntelliJ Platform community space](https://platform.jetbrains.com/)
- Having any difficulties?
Join the brand new
[](https://gitter.im/JetBrains/ideavim?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
for IdeaVim developers and contributors!
Ask any questions in [GitHub discussions](https://github.com/JetBrains/ideavim/discussions) or [IntelliJ Platform community space](https://platform.jetbrains.com/).
OK, ready to do some coding?
@@ -41,7 +65,7 @@ We've prepared some useful configurations for you:
And here are useful gradle commands:
*`./gradlew runIde` — start the dev version of IntelliJ IDEA with IdeaVim installed.
*`./gradlew test` — run tests.
*`./gradlew test -x :tests:property-tests:test -x :tests:long-running-tests:test` — run tests.
*`./gradlew buildPlugin` — build the plugin. The result will be located in `build/distributions`. This file can be
installed by using `Settings | Plugin | >Gear Icon< | Install Plugin from Disk...`. You can stay with your personal build
for a few days or send it to a friend for testing.
@@ -54,20 +78,24 @@ for a few days or send it to a friend for testing.
- Read the javadoc for the `@VimBehaviorDiffers` annotation in the source code and fix the corresponding functionality.
- Implement one of the requested [#vim plugin](https://youtrack.jetbrains.com/issues/VIM?q=%23Unresolved%20tag:%20%7Bvim%20plugin%7D%20sort%20by:%20votes%20)s.
> :small_orange_diamond: Selected an issue to work on? Leave a comment in a YouTrack ticket or create a draft PR
> to indicate that you've started working on it so that you might get additional guidance and feedback from the maintainers.
> :small_orange_diamond: You may leave a comment in the YouTrack ticket or open a draft PR if you’d like early feedback
> or want to let maintainers know you’ve started working on an issue. Otherwise, simply open a PR.
## Where to start in the codebase
If you are looking for:
- Vim commands (`w`, `<C-O>`, `p`, etc.):
- Any particular command:`package-info.java`.
- Any particular command:
- [Commands common for Fleet and IdeaVim](vim-engine/src/main/resources/ksp-generated/engine_commands.json)
- [IdeaVim only commands](src/main/resources/ksp-generated/intellij_commands.json)
- How commands are executed in common: `EditorActionHandlerBase`.
- Key mapping: `KeyHandler.handleKey()`.
- Ex commands (`:set`, `:s`, `:nohlsearch`):
- Any particular ex command: package `com.maddyhome.idea.vim.ex.handler`.
- Any particular command:
- [Commands common for Fleet and IdeaVim](vim-engine/src/main/resources/ksp-generated/engine_ex_commands.json)
- [IdeaVim only commands](src/main/resources/ksp-generated/intellij_ex_commands.json)
- Vim script grammar: `Vimscript.g4`.
- Vim script parsing: package `com.maddyhome.idea.vim.vimscript.parser`.
- Vim script executor: `Executor`.
@@ -78,7 +106,7 @@ If you are looking for:
- Common features:
- State machine. How every particular keystroke is parsed in IdeaVim: `KeyHandler.handleKey()`.
@@ -106,7 +134,7 @@ Cras id tellus in ex imperdiet egestas.
carets, dollar motion, etc.
##### Neovim
IdeaVim has an experimental integration with neovim in tests. Tests that are performed with `doTest` also executed in
IdeaVim has an integration with neovim in tests. Tests that are performed with `doTest` also executed in
neovim instance, and the state of IdeaVim is asserted to be the same as the state of neovim.
- Only tests that use `doTest` are checked with neovim.
- Tests with `@VimBehaviorDiffers` or `@TestWithoutNeovim` annotations don't use neovim.
@@ -132,14 +160,15 @@ We also support proper command mappings (functions are mapped to `<Plug>...`), t
- Magic is supported as well. See `Magic`.
## Fleet refactoring
At the moment, IdeaVim is under an active refactoring aiming to split IdeaVim into two modules: vim-engine and IdeaVim.
## Fleet
The IdeaVim plugin is divided into two main modules: IdeaVim and vim-engine.
IdeaVim serves as a plugin for JetBrains IDEs, while vim-engine is an IntelliJ Platform-independent Vim engine.
This engine is utilized in both the Vim plugin for Fleet and IdeaVim.
If you develop a plugin that depends on IdeaVim: We have an instrument to check that our changes don't affect
the plugins in the marketplace. Also, we commit to support currently used API at least till the end of 2022.
the plugins in the marketplace.
If you still encounter any issues with the newer versions of IdeaVim, please [contact maintainers](https://github.com/JetBrains/ideavim#contact-maintainers).
We kindly ask you not to use anything from the new API (like `VimEditor`, `injector`) because at the moment we don't
guarantee the compatibility of this API in the future versions.
-----
@@ -162,6 +191,7 @@ This is just terrible. [You know what to do](https://github.com/JetBrains/ideavi
The power of contributing drives IdeaVim :muscle:. Even small contributions matter!
See [CONTRIBUTING.md](CONTRIBUTING.md) to start bringing your value to the project.
See the contribution guide in [CONTRIBUTING.md](CONTRIBUTING.md) to start bringing your value to the project.
😎 In 2025, we launched a rewards program. See the guide for details.
Authors
-------
@@ -325,11 +308,11 @@ IdeaVim tips and tricks
-`set ideajoin` to enable join via the IDE. See the [examples](https://jb.gg/f9zji9).
- Make sure `ideaput` is enabled for `clipboard` to enable native IJ insertion in Vim.
- Sync IJ bookmarks and IdeaVim global marks: `set ideamarks` (works for marks with capital letters only)
- Check out more [ex commands](https://github.com/JetBrains/ideavim/wiki/%22set%22-commands).
- Check out more [ex commands](https://github.com/JetBrains/ideavim/wiki/set-commands).
- Use your vim settings with IdeaVim. Put `source ~/.vimrc` in `~/.ideavimrc`.
- Control the status bar icon via the [`ideastatusicon` option](https://github.com/JetBrains/ideavim/wiki/%22set%22-commands).
- Not familiar with the default behaviour during a refactoring? See the [`idearefactormode` option](https://github.com/JetBrains/ideavim/wiki/%22set%22-commands).
- Control the status bar icon via the [`ideastatusicon` option](https://github.com/JetBrains/ideavim/wiki/set-commands).
- Not familiar with the default behaviour during a refactoring? See the [`idearefactormode` option](https://github.com/JetBrains/ideavim/wiki/set-commands).
Some facts about Vim
-------
@@ -369,6 +352,14 @@ is the full list of synonyms.
- Fancy constants for [undolevels](https://vimhelp.org/options.txt.html#%27undolevels%27):
> The local value is set to -123456 when the global value is to be used.
- Vi (not Vim) is a POSIX standard, and [has a spec](https://pubs.opengroup.org/onlinepubs/9699919799/utilities/vi.html)! Vim is mostly POSIX compliant when Vi compatibility is selected with the `'compatible'` option, but there are still some differences that can be changed with `'copoptions'`. The spec is interesting because it documents the behaviour of different commands in a stricter style than the user documentation, describing the current line and column after the command, for example. [More details can be found by reading `:help posix`](https://vimhelp.org/vi_diff.txt.html#posix).
- The Vim documentation contains many easter eggs. We encounter them occasionally, but GitHub user mikesmithgh has compiled a substantial collection [here](https://github.com/mikesmithgh/vimpromptu).
- In addition to `:call err_teapot()`, which returns `E418: I'm a teapot`, there is also `:call err_teapot(1)`, which returns `E503: Coffee is currently not available`. Naturally, this is also supported in IdeaVim.
- Insert mode has all `Ctrl` keys mapped, except `Ctrl-B`. In the documentation, it is marked as **"CTRL-B in Insert
mode gone"**. Call `:h i_CTRL-B-gone` in Vim to read why `Ctrl-B` was removed.
// Using Rider as a target IntelliJ Platform with `useInstaller = true` is currently not supported, please set `useInstaller = false` instead. See: https://github.com/JetBrains/intellij-platform-gradle-plugin/issues/1852
useInstaller = false
}
// Note that it is also possible to use local("...") to compile against a locally installed IDE
// E.g. local("/Users/{user}/Applications/IntelliJ IDEA Ultimate.app")
// Or something like: intellijIdeaUltimate(ideaVersion)
We’ve launched a program to reward quality contributions with a one-year All Products Pack subscription. Learn more at: <a href="https://github.com/JetBrains/ideavim/blob/master/CONTRIBUTING.md">CONTRIBUTING.md</a> .
@@ -16,10 +16,137 @@ in `~/.ideavimrc`. E.g. `set nosurround`.
Available plugins:
<details>
<summary><h2>easymotion</h2></summary>
<summary><h2>anyobject: Useful text objects like functions, arguments, classes, loops, list items, block comments, and more.</h2></summary>
### Summary:
An extension for IdeaVim plugin that adds useful text objects to improve your productivity on JetBrains IDEs.
Text objects allow a more efficient way of communicating edition or selection actions in the editor. Instead of thinking in terms of characters, words, lines, or paragraphs, use more advance text constructs like quoted text, text between brackets, items in a collection, or programming language constructs like arguments, classes, functions, loops, or comments.
<summary><h2>dial: Advanced text increment and decrement functionality.</h2></summary>
### Summary:
IdeaVim extension with advanced text increment and decrement functionality. It enhances the standard increment/decrement functionality found in Vim editors by adding support for complex text patterns beyond simple numbers.
Cycle through related values from various text elements, including numbers, dates, boolean values, operators, and programming language-specific keywords.
Original plugin: [vim-multiple-cursors](https://github.com/terryma/vim-multiple-cursors).
### Setup:
- Add the following command to `~/.ideavimrc`: `Plug 'terryma/vim-multiple-cursors'`
<details>
@@ -127,7 +350,7 @@ Original plugin: [vim-multiple-cursors](https://github.com/terryma/vim-multiple-
<br/>
<code>set multiple-cursors</code>
</details>
### Instructions
At the moment, the default key binds for this plugin do not get mapped correctly in IdeaVim (see [VIM-2178](https://youtrack.jetbrains.com/issue/VIM-2178)). To enable the default key binds, add the following to your `.ideavimrc` file...
# Quick Start Guide for IdeaVim Plugin Development
> **⚠️ EXPERIMENTAL API WARNING**
>
> The Plugin API is currently in an **experimental stage** and is not yet recommended for production use.
>
> - The API is subject to breaking changes without notice
> - Features may be added, modified, or removed in future releases
> - Documentation may not fully reflect the current implementation
> - Use at your own risk for experimental purposes only
>
> We welcome feedback and bug reports to help improve the API, but please be aware that stability is not guaranteed at this time.
This guide will help you get started with developing plugins for IdeaVim.
We'll cover the essential concepts and show you how to create a simple plugin.
## Setting Up Your First Plugin
### 1. Project Setup
For now, you can create plugin in the IdeaVim extensions package - [link](https://github.com/JetBrains/ideavim/tree/4764ffbbf545607ad4a5c482d74e0219002a5aca/src/main/java/com/maddyhome/idea/vim/extension).
### 2. Create the Plugin Entry Point
The entry point for an IdeaVim plugin is a function annotated with `@VimPlugin`:
```kotlin
@VimPlugin(name="MyFirstPlugin")
funVimApi.init(){
// Plugin initialization code goes here
}
```
Here we will register mappings, listeners, commands etc.
### 3. Add Functionality
Let's add a simple mapping that displays a message in the output panel:
> The Plugin API is currently in an **experimental stage** and is not yet recommended for production use.
>
> - The API is subject to breaking changes without notice
> - Features may be added, modified, or removed in future releases
> - Documentation may not fully reflect the current implementation
> - Use at your own risk for experimental purposes only
>
> We welcome feedback and bug reports to help improve the API, but please be aware that stability is not guaranteed at this time.
## VimApi
The `VimApi` class is the main entry point for interacting with the Vim editor. It provides access to various functionalities like variable management, window operations, and text manipulation.
### Properties
| Property | Type | Description |
|----------|------|-------------|
| `mode` | `Mode` | The current mode of the Vim editor. |
| `tabCount` | `Int` | Gets the number of tabs in the current window. |
| `currentTabIndex` | `Int?` | The index of the current tab or null if there is no tab selected or no tabs are open. |
| `getVariable<T : Any>(name: String): T?` | Gets a variable with the specified name and scope. | The variable value or null if not found. |
| `setVariable<T : Any>(name: String, value: T)` | Sets a variable with the specified name and value. In Vim, this is equivalent to `let varname = value`. | None |
| `exportOperatorFunction(name: String, function: suspend VimApi.() -> Boolean)` | Exports a function as an operator function. | None |
| `setOperatorFunction(name: String)` | Sets the operator function to use. | None |
| `normal(command: String)` | Executes a normal mode command. | None |
#### Editor Operations
| Method | Description | Return Value |
|--------|-------------|--------------|
| `editor<T>(block: EditorScope.() -> T): T` | Executes a block of code in the context of the current editor. | The result of the block. |
| `forEachEditor<T>(block: EditorScope.() -> T): List<T>` | Executes a block of code for each open editor. | A list of results from each editor. |
#### Scope Access
| Method | Description | Return Value |
|--------|-------------|--------------|
| `mappings(block: MappingScope.() -> Unit)` | Executes a block of code in the mapping scope. | None |
| `listeners(block: ListenersScope.() -> Unit)` | Executes a block of code in the listeners scope. | None |
| `outputPanel(block: OutputPanelScope.() -> Unit)` | Executes a block of code in the output panel scope. | None |
| `modalInput(): ModalInput` | Gets the modal input scope. | The modal input scope. |
| `commandLine(block: CommandLineScope.() -> Unit)` | Executes a block of code in the command line scope. | None |
| `option<T>(block: OptionScope.() -> T): T` | Executes a block of code in the option scope. | The result of the block execution. |
| `digraph(block: DigraphScope.() -> Unit)` | Executes a block of code in the digraph scope. | None |
#### Tab Management
| Method | Description | Return Value |
|--------|-------------|--------------|
| `removeTabAt(indexToDelete: Int, indexToSelect: Int)` | Removes a tab at the specified index and selects another tab. | None |
| `moveCurrentTabToIndex(index: Int)` | Moves the current tab to the specified index. | None |
| `closeAllExceptCurrentTab()` | Closes all tabs except the current one. | None |
#### Pattern Matching
| Method | Description | Return Value |
|--------|-------------|--------------|
| `matches(pattern: String, text: String, ignoreCase: Boolean = false): Boolean` | Checks if a pattern matches a text. | True if the pattern matches the text, false otherwise. |
| `getAllMatches(text: String, pattern: String): List<Pair<Int, Int>>` | Finds all matches of a pattern in a text. | A list of pairs representing the start and end offsets of each match. |
#### Window Management
| Method | Description | Return Value |
|--------|-------------|--------------|
| `selectNextWindow()` | Selects the next window in the editor. | None |
| `selectPreviousWindow()` | Selects the previous window in the editor. | None |
| `selectWindow(index: Int)` | Selects a window by its index. | None |
| `splitWindowVertically(filename: String? = null)` | Splits the current window vertically and optionally opens a file in the new window. | None |
| `splitWindowHorizontally(filename: String? = null)` | Splits the current window horizontally and optionally opens a file in the new window. | None |
| `closeAllExceptCurrentWindow()` | Closes all windows except the current one. | None |
| `closeCurrentWindow()` | Closes the current window. | None |
| `closeAllWindows()` | Closes all windows in the editor. | None |
| `execute(script: String): Boolean` | Parses and executes the given Vimscript string. It can be used to execute ex commands, such as `:set`, `:map`, etc. | The result of the execution, which can be Success or Error. |
| `command(command: String, block: VimApi.(String) -> Unit)` | Defines a new command. | None |
#### Data Storage
| Method | Description | Return Value |
|--------|-------------|--------------|
| `getDataFromWindow<T>(key: String): T?` | Gets keyed data from a Vim window. | The data associated with the key, or null if no data is found. |
| `putDataToWindow<T>(key: String, data: T)` | Stores keyed user data in a Vim window. | None |
| `getDataFromBuffer<T>(key: String): T?` | Gets data from buffer. Vim stores there buffer scoped (`b:`) variables and local options. | The data associated with the key, or null if no data is found. |
| `putDataToBuffer<T>(key: String, data: T)` | Puts data to buffer. Vim stores there buffer scoped (`b:`) variables and local options. | None |
| `getDataFromTab<T>(key: String): T?` | Gets data from tab (group of windows). Vim stores there tab page scoped (`t:`) variables. | The data associated with the key, or null if no data is found. |
| `putDataToTab<T>(key: String, data: T)` | Puts data to tab (group of windows). Vim stores there tab page scoped (`t:`) variables. | None |
| `getOrPutWindowData<T>(key: String, provider: () -> T): T` | Gets data from window or puts it if it doesn't exist. | The existing data or the newly created data. |
| `getOrPutBufferData<T>(key: String, provider: () -> T): T` | Gets data from buffer or puts it if it doesn't exist. | The existing data or the newly created data. |
| `getOrPutTabData<T>(key: String, provider: () -> T): T` | Gets data from tab or puts it if it doesn't exist. | The existing data or the newly created data. |
#### File Operations
| Method | Description | Return Value |
|--------|-------------|--------------|
| `saveFile()` | Saves the current file. In Vim, this is equivalent to the `:w` command. | None |
| `closeFile()` | Closes the current file. In Vim, this is equivalent to the `:q` command. | None |
#### Text Navigation
| Method | Description | Return Value |
|--------|-------------|--------------|
| `getNextCamelStartOffset(chars: CharSequence, startIndex: Int, count: Int = 1): Int?` | Finds the start offset of the next word in camel case or snake case text. | The offset of the next word start, or null if not found. |
| `getPreviousCamelStartOffset(chars: CharSequence, endIndex: Int, count: Int = 1): Int?` | Finds the start offset of the previous word in camel case or snake case text. | The offset of the previous word start, or null if not found. |
| `getNextCamelEndOffset(chars: CharSequence, startIndex: Int, count: Int = 1): Int?` | Finds the end offset of the next word in camel case or snake case text. | The offset of the next word end, or null if not found. |
| `getPreviousCamelEndOffset(chars: CharSequence, endIndex: Int, count: Int = 1): Int?` | Finds the end offset of the previous word in camel case or snake case text. | The offset of the previous word end, or null if not found. |
## EditorScope
The `EditorScope` class provides access to read and write operations on the editor. It serves as a bridge between the read-only and transaction-based operations.
### Methods
| Method | Description | Return Value |
|--------|-------------|--------------|
| `read<T>(block: suspend Read.() -> T): Deferred<T>` | Executes a block of code in the context of read operations. This allows for reading the editor state without modifying it. | A Deferred result of the block execution. |
| `change(block: suspend Transaction.() -> Unit): Job` | Executes a block of code in the context of transaction operations. This allows for modifying the editor state. | A Job representing the asynchronous operation. |
## ReadScope
The `ReadScope` interface provides read-only access to the editor content and state. It includes methods for navigating text, working with carets, and querying editor information.
| `forEachCaret<T>(block: suspend CaretRead.() -> T): List<T>` | Executes a block of code for each caret in the editor. | A list of results from each caret. |
| `with<T>(caretId: CaretId, block: suspend CaretRead.() -> T): T` | Executes a block of code with a specific caret. | Result from caret. |
| `withPrimaryCaret<T>(block: suspend CaretRead.() -> T): T` | Executes a block of code with the primary caret. | Result from caret. |
#### Line Operations
| Method | Description | Return Value |
|--------|-------------|--------------|
| `getLineStartOffset(line: Int): Int` | Gets the offset of the start of a line. | The offset of the line start. |
| `getLineEndOffset(line: Int, allowEnd: Boolean): Int` | Gets the offset of the end of a line. | The offset of the line end. |
| `getLine(offset: Int): Line` | Gets the line at the specified offset. | The Line object. |
#### Mark and Jump Operations
| Method | Description | Return Value |
|--------|-------------|--------------|
| `getGlobalMark(char: Char): Mark?` | Gets a global mark by its character key. | The mark, or null if the mark doesn't exist. |
| `getJump(count: Int = 0): Jump?` | Gets a jump from the jump list. | The jump, or null if there is no jump at the specified position. |
#### Scrolling Operations
| Method | Description | Return Value |
|--------|-------------|--------------|
| `scrollCaretIntoView()` | Scrolls the caret into view. | None |
| `scrollVertically(lines: Int): Boolean` | Scrolls the editor by a specified number of lines. | True if the scroll was successful, false otherwise. |
| `scrollLineToTop(line: Int, start: Boolean): Boolean` | Scrolls the current line to the top of the display. | True if the scroll was successful, false otherwise. |
| `scrollLineToMiddle(line: Int, start: Boolean): Boolean` | Scrolls the current line to the middle of the display. | True if the scroll was successful, false otherwise. |
| `scrollLineToBottom(line: Int, start: Boolean): Boolean` | Scrolls the current line to the bottom of the display. | True if the scroll was successful, false otherwise. |
| `scrollHorizontally(columns: Int): Boolean` | Scrolls the editor horizontally by a specified number of columns. | True if the scroll was successful, false otherwise. |
| `scrollCaretToLeftEdge(): Boolean` | Scrolls the editor to position the caret column at the left edge of the display. | True if the scroll was successful, false otherwise. |
| `scrollCaretToRightEdge(): Boolean` | Scrolls the editor to position the caret column at the right edge of the display. | True if the scroll was successful, false otherwise. |
#### Text Navigation
| Method | Description | Return Value |
|--------|-------------|--------------|
| `getNextParagraphBoundOffset(startLine: Int, count: Int = 1, includeWhitespaceLines: Boolean = true): Int?` | Find the next paragraph-bound offset in the editor. | The offset of the next paragraph bound, or null if not found. |
| `getNextSentenceStart(startOffset: Int, count: Int = 1, includeCurrent: Boolean, requireAll: Boolean = true): Int?` | Finds the next sentence start in the editor from the given offset. | The offset of the next sentence start, or null if not found. |
| `getNextSectionStart(startLine: Int, marker: Char, count: Int = 1): Int` | Find the next section in the editor. | The offset of the next section. |
| `getPreviousSectionStart(startLine: Int, marker: Char, count: Int = 1): Int` | Find the start of the previous section in the editor. | The offset of the previous section. |
| `getNextSentenceEnd(startOffset: Int, count: Int = 1, includeCurrent: Boolean, requireAll: Boolean = true): Int?` | Find the next sentence end from the given offset. | The offset of the next sentence end, or null if not found. |
| `getNextWordStartOffset(startOffset: Int, count: Int = 1, isBigWord: Boolean): Int?` | Find the next word in the editor's document, from the given starting point. | The offset of the next word, or null if not found. |
| `getNextWordEndOffset(startOffset: Int, count: Int = 1, isBigWord: Boolean, stopOnEmptyLine: Boolean = true): Int` | Find the end offset of the next word in the editor's document. | The offset of the next word end. |
| `getNextCharOnLineOffset(startOffset: Int, count: Int = 1, char: Char): Int` | Find the next character on the current line. | The offset of the next character, or -1 if not found. |
| `getNearestWordOffset(startOffset: Int): Range?` | Find the word at or nearest to the given offset. | The range of the word, or null if not found. |
| `getParagraphRange(line: Int, count: Int = 1, isOuter: Boolean): Range?` | Returns range of a paragraph containing the given line. | The paragraph text range, or null if not found. |
| `getBlockQuoteInLineRange(startOffset: Int, quote: Char, isOuter: Boolean): Range?` | Find a block quote in the current line. | The range of the block quote, or null if not found. |
#### Pattern Matching
| Method | Description | Return Value |
|--------|-------------|--------------|
| `findAll(pattern: String, startLine: Int, endLine: Int, ignoreCase: Boolean = false): List<Range>` | Finds all occurrences of the given pattern within a specified line range. | A list of Ranges representing all matches found. |
| `findPattern(pattern: String, startOffset: Int, count: Int = 1, backwards: Boolean = false): Range?` | Finds text matching the given Vim-style regular expression pattern. | A Range representing the matched text, or null if no match is found. |
## Transaction
The `Transaction` interface provides methods for modifying the editor content and state. It includes operations for working with carets, highlights, marks, and jumps.
| `forEachCaret<T>(block: suspend CaretTransaction.() -> T): List<T>` | Executes a block of code for each caret in the editor. | A list of results from each caret. |
| `with<T>(caretId: CaretId, block: suspend CaretTransaction.() -> T): T` | Executes a block of code with a specific caret. | Result from caret. |
| `withPrimaryCaret<T>(block: suspend CaretTransaction.() -> T): T` | Executes a block of code with the primary caret. | Result from caret. |
| `addCaret(offset: Int): CaretId` | Adds a new caret at the specified offset. | The ID of the newly created caret. |
| `removeCaret(caretId: CaretId)` | Removes a caret with the specified ID. | None |
#### Highlighting
| Method | Description | Return Value |
|--------|-------------|--------------|
| `addHighlight(startOffset: Int, endOffset: Int, backgroundColor: Color?, foregroundColor: Color?): HighlightId` | Adds a highlight to the editor. | The ID of the newly created highlight. |
| `removeHighlight(highlightId: HighlightId)` | Removes a highlight with the specified ID. | None |
#### Mark Operations
| Method | Description | Return Value |
|--------|-------------|--------------|
| `setMark(char: Char): Boolean` | Sets a mark at the current position for each caret in the editor. | True if the mark was successfully set, false otherwise. |
| `removeMark(char: Char)` | Removes a mark for all carets in the editor. | None |
| `setGlobalMark(char: Char): Boolean` | Sets a global mark at the current position. | True if the mark was successfully set, false otherwise. |
| `removeGlobalMark(char: Char)` | Removes a global mark. | None |
| `setGlobalMark(char: Char, offset: Int): Boolean` | Sets a global mark at the specified offset. | True if the mark was successfully set, false otherwise. |
| `resetAllMarks()` | Resets all marks. | None |
#### Jump List Operations
| Method | Description | Return Value |
|--------|-------------|--------------|
| `addJump(jump: Jump, reset: Boolean)` | Adds a specific jump to the jump list. | None |
| `removeJump(jump: Jump)` | Removes a jump from the jump list. | None |
| `dropLastJump()` | Removes the last jump from the jump list. | None |
| `clearJumps()` | Clears all jumps from the jump list. | None |
## CaretRead
The `CaretRead` interface provides read-only access to a caret in the editor. It includes methods for working with registers, marks, scrolling, and text navigation.
### Properties
| Property | Type | Description |
|----------|------|-------------|
| `caretId` | `CaretId` | The unique identifier for this caret. |
| `offset` | `Int` | The current offset (position) of the caret in the document. |
| `selection` | `Range` | The current selection range of the caret. |
| `line` | `Line` | Information about the current line where the caret is positioned. |
| `lastSelectedReg` | `Char` | The last register that was selected for operations. Example: After using `"ay` to yank into register 'a', this would return 'a'. In VimScript, variable `v:register` contains this value. |
| `defaultRegister` | `Char` | The default register used when no register is explicitly specified. In Vim, this is typically the unnamed register ("). |
| `isRegisterSpecifiedExplicitly` | `Boolean` | Indicates whether the current register was explicitly specified by the user. Example: After `"ay`, this would be true; after just `y`, this would be false. |
| `selectionMarks` | `Range?` | The marks for the current visual selection. In Vim, these are the '<and'> marks. |
| `changeMarks` | `Range?` | The marks for the last change. In Vim, these are the '[ and '] marks. |
| `localMarks` | `Set<Mark>` | All local marks for the current caret. |
### Methods
#### Register Operations
| Method | Description | Return Value |
|--------|-------------|--------------|
| `selectRegister(register: Char): Boolean` | Selects a register for subsequent operations. Example: In Vim, pressing `"a` before an operation selects register 'a'. | True if the register was successfully selected, false otherwise. |
| `resetRegisters()` | Resets all registers to their default state. | None |
| `isWritable(register: Char): Boolean` | Checks if a register is writable. Some registers in Vim are read-only. | True if the register is writable, false otherwise. |
| `isSystemClipboard(register: Char): Boolean` | Checks if a register is connected to the system clipboard. In Vim, registers '+' and '*' are connected to the system clipboard. | True if the register is connected to the system clipboard, false otherwise. |
| `isPrimaryRegisterSupported(): Boolean` | Checks if the primary selection register is supported. Example: On Linux, using `"*y` yanks text to the primary selection. | True if the primary selection register is supported, false otherwise. |
| `getReg(register: Char): String?` | Gets the text content of a register. | The text content of the register, or null if the register is empty or doesn't exist. |
| `getRegType(register: Char): TextType?` | Gets the type of text stored in a register (character-wise, line-wise, or block-wise). | The type of text in the register, or null if the register is empty or doesn't exist. |
| `setReg(register: Char, text: String, textType: TextType = TextType.CHARACTER_WISE): Boolean` | Sets the text content and type of a register. | True if the register was successfully set, false otherwise. |
#### Mark Operations
| Method | Description | Return Value |
|--------|-------------|--------------|
| `getMark(char: Char): Mark?` | Gets a mark by its character key for the current caret. | The mark, or null if the mark doesn't exist. |
| `setMark(char: Char): Boolean` | Sets a mark at the current caret position. | True if the mark was successfully set, false otherwise. |
| `setMark(char: Char, offset: Int): Boolean` | Sets a mark at the specified offset. | True if the mark was successfully set, false otherwise. |
| `removeLocalMark(char: Char)` | Removes a local mark for the current caret. | None |
| `resetAllMarksForCaret()` | Resets all marks for the current caret. | None |
#### Scrolling Operations
| Method | Description | Return Value |
|--------|-------------|--------------|
| `scrollFullPage(pages: Int): Boolean` | Scrolls a full page up or down. Positive values scroll down, negative values scroll up. | True if the scroll was successful, false otherwise. |
| `scrollHalfPageUp(lines: Int): Boolean` | Scrolls half a page up. | True if the scroll was successful, false otherwise. |
| `scrollHalfPageDown(lines: Int): Boolean` | Scrolls half a page down. | True if the scroll was successful, false otherwise. |
| `selectWindowHorizontally(relativePosition: Int)` | Selects a window in the same row as the current window. Positive values select windows to the right, negative values select windows to the left. | None |
| `selectWindowInVertically(relativePosition: Int)` | Selects a window in the same column as the current window. Positive values select the windows below, negative values select the windows above. | None |
#### Text Navigation
| Method | Description | Return Value |
|--------|-------------|--------------|
| `getNextParagraphBoundOffset(count: Int = 1, includeWhitespaceLines: Boolean = true): Int?` | Finds the offset of the next paragraph boundary. | The offset of the next paragraph bound, or null if not found. |
| `getNextSentenceStart(count: Int = 1, includeCurrent: Boolean, requireAll: Boolean = true): Int?` | Finds the next sentence start in the editor from the given offset. | The offset of the next sentence start, or null if not found. |
| `getNextSectionStart(marker: Char, count: Int = 1): Int` | Find the next section in the editor. | The offset of the next section. |
| `getPreviousSectionStart(marker: Char, count: Int = 1): Int` | Find the start of the previous section in the editor. | The offset of the previous section. |
| `getNextSentenceEnd(count: Int = 1, includeCurrent: Boolean, requireAll: Boolean = true): Int?` | Finds the end offset of the next sentence from the current caret position. | The offset of the next sentence end, or null if not found. |
| `getMethodEndOffset(count: Int = 1): Int` | Finds the end offset of the next method from the current caret position. | The offset of the end of the next method. |
| `getMethodStartOffset(count: Int = 1): Int` | Finds the start offset of the next method from the current caret position. | The offset of the start of the next method. |
| `getNextCharOnLineOffset(count: Int = 1, char: Char): Int` | Finds the next occurrence of a specific character on the current line. | The offset of the found character, or -1 if not found. |
| `getNearestWordOffset(): Range?` | Finds the word at or nearest to the current caret position. | A Range representing the found word, or null if no word is found. |
| `getWordTextObjectRange(count: Int = 1, isOuter: Boolean, isBigWord: Boolean): Range` | Find the range of the word text object at the location of the caret. | The range of the word text object. |
| `getSentenceRange(count: Int = 1, isOuter: Boolean): Range` | Find the range of the sentence text object at the location of the caret. | The range of the sentence text object. |
| `getParagraphRange(count: Int = 1, isOuter: Boolean): Range?` | Returns range of a paragraph containing the caret. | The paragraph text range, or null if not found. |
| `getBlockTagRange(count: Int = 1, isOuter: Boolean): Range?` | Find the range of a block tag at the location of the caret. | The range of the block tag, or null if not found. |
| `getBlockQuoteInLineRange(quote: Char, isOuter: Boolean): Range?` | Find a block quote in the current line at the location of the caret. | The range of the block quote, or null if not found. |
| `getNextMisspelledWordOffset(count: Int = 1): Int` | Finds the offset of the next misspelled word from the current caret position. | The offset of the next misspelled word. |
## CaretTransaction
The `CaretTransaction` interface extends `CaretRead` and provides methods for modifying the caret and text in the editor. It includes operations for updating the caret position, inserting text, replacing text, and deleting text.
### Methods
#### Caret Operations
| Method | Description | Return Value |
|--------|-------------|--------------|
| `updateCaret(offset: Int, selection: Range.Simple? = null)` | Updates the caret position and optionally sets a selection. If a selection is provided, the caret will have this selection after moving to the new offset. If no selection is provided, any existing selection will be removed. | None |
#### Text Operations
| Method | Description | Return Value |
|--------|-------------|--------------|
| `insertText(position: Int, text: String, caretAtEnd: Boolean = true, insertBeforeCaret: Boolean = false): Boolean` | Inserts text at the specified position in the document. | True if the insertion was successful, false otherwise. |
| `replaceText(startOffset: Int, endOffset: Int, text: String): Boolean` | Replaces text between the specified offsets with new text. | True if the replacement was successful, false otherwise. |
| `replaceTextBlockwise(range: Range.Block, text: List<String>)` | Replaces text in multiple ranges (blocks) with new text. | None |
| `deleteText(startOffset: Int, endOffset: Int): Boolean` | Deletes text between the specified offsets. | True if the deletion was successful, false otherwise. |
#### Jump Operations
| Method | Description | Return Value |
|--------|-------------|--------------|
| `addJump(reset: Boolean)` | Adds a jump with the current caret's position to the jump list. | None |
| `saveJumpLocation()` | Saves the location of the current caret to the jump list and sets the ' mark. | None |
## OptionScope
The `OptionScope` interface provides comprehensive methods for managing Vim options. It supports different scopes for options (global, local, and effective) and allows for type-safe access to option values. The `option` function returns a value, making it easy to retrieve option values directly.
| `get<T>(name: String): T` | Gets the value of an option with the specified type. In Vim, options can be accessed with the `&` prefix. Example: `&ignorecase` returns the value of the 'ignorecase' option. | The value of the option. Throws IllegalArgumentException if the option doesn't exist or type is wrong. |
| `set<T>(name: String, value: T)` | Sets the effective value of an option with the specified type. In Vim, this is equivalent to `:set option=value`. Example: `:set ignorecase` or `let &ignorecase = 1` | None |
| `setGlobal<T>(name: String, value: T)` | Sets the global value of an option with the specified type. In Vim, this is equivalent to `:setglobal option=value`. Example: `:setglobal ignorecase` or `let &g:ignorecase = 1` | None |
| `setLocal<T>(name: String, value: T)` | Sets the local value of an option with the specified type. In Vim, this is equivalent to `:setlocal option=value`. Example: `:setlocal ignorecase` or `let &l:ignorecase = 1` | None |
| `reset(name: String)` | Resets an option to its default value. In Vim, this is equivalent to `:set option&`. Example: `:set ignorecase&` resets the 'ignorecase' option to its default value. | None |
### List Option Methods
These extension functions provide convenient ways to manipulate comma-separated list options (like `virtualedit`, `whichwrap`, etc.):
The `OutputPanelScope` interface provides methods for interacting with the Vim output panel. The output panel is used to display text output from Vim commands and operations.
### Properties
| Property | Type | Description |
|----------|------|-------------|
| `text` | `String` | The text displayed in the output panel. |
| `label` | `String` | The label text displayed at the bottom of the output panel. This is used for status information like "-- MORE --" to indicate that there is more content to scroll through. |
### Methods
| Method | Description | Return Value |
|--------|-------------|--------------|
| `setText(text: String)` | Sets the text content of the output panel. This replaces any existing text in the panel with the provided text. | None |
| `appendText(text: String, startNewLine: Boolean = false)` | Appends text to the existing content of the output panel. If startNewLine is true and there is existing text, a newline character will be inserted before the appended text. | None |
| `setLabel(label: String)` | Sets the label text at the bottom of the output panel. | None |
| `clearText()` | Clears all text from the output panel. | None |
## ModalInput
The `ModalInput` interface provides methods for creating and managing modal input dialogs, which can be used to get user input in a Vim-like way.
| `updateLabel(block: (String) -> String): ModalInput` | Updates the label of the modal input dialog using the provided function. | The ModalInput instance for method chaining. |
| `repeatWhile(condition: () -> Boolean): ModalInput` | Repeats the modal input dialog while the provided condition is true. | The ModalInput instance for method chaining. |
| `repeat(count: Int): ModalInput` | Repeats the modal input dialog the specified number of times. | The ModalInput instance for method chaining. |
| `inputString(label: String, handler: VimApi.(String) -> Unit)` | Creates a modal input dialog with the given label and handler. The handler will be executed after the user presses ENTER. | None |
| `inputChar(label: String, handler: VimApi.(Char) -> Unit)` | Creates a modal input dialog with the given label and handler. The handler will be executed after the user enters a character. | None |
| `closeCurrentInput(refocusEditor: Boolean = true): Boolean` | Closes the current modal input dialog, if any. If refocusEditor is true, the editor will be refocused after closing the dialog. | True if a dialog was closed, false otherwise. |
## ListenerScope
The `ListenerScope` interface provides methods for registering callbacks for various events in the Vim editor, such as mode changes, yanking text, editor lifecycle events, and more.
| `onModeChange(callback: suspend VimApi.(Mode) -> Unit)` | Registers a callback that is invoked when the editor mode changes (e.g., from Normal to Insert). | None |
| `onYank(callback: suspend VimApi.(Map<CaretId, Range.Simple>) -> Unit)` | Registers a callback that is invoked when text is yanked. The callback receives a map of caret IDs to yanked text ranges. | None |
| `onIdeaVimEnabled(callback: suspend VimApi.() -> Unit)` | Registers a callback that is invoked when IdeaVim is enabled. | None |
| `onIdeaVimDisabled(callback: suspend VimApi.() -> Unit)` | Registers a callback that is invoked when IdeaVim is disabled. | None |
## DigraphScope
The `DigraphScope` interface provides access to Vim's digraph functionality. Digraphs are special character combinations that produce a single character, often used for entering non-ASCII characters.
### Methods
| Method | Description | Return Value |
|--------|-------------|--------------|
| `getCharacter(ch1: Char, ch2: Char): Int` | Gets the character for a digraph. | The Unicode codepoint of the character represented by the digraph, or the codepoint of ch2 if no digraph is found. |
| `clearCustomDigraphs()` | Clears all custom digraphs. | None |
## CommandLineScope
The `CommandLineScope` class provides methods for interacting with the Vim command line. The command line is used for entering Ex commands, search patterns, and other input.
| `input(prompt: String, finishOn: Char? = null, callback: VimApi.(String) -> Unit)` | Reads input from the command line and processes it with the provided function. | None |
| `read<T>(block: suspend CommandLineRead.() -> T): Deferred<T>` | Executes a block of code in the context of read operations on the command line. This allows for reading the command line state without modifying it. | A Deferred result of the block execution. |
| `change(block: suspend CommandLineTransaction.() -> Unit): Job` | Executes a block of code in the context of transaction operations on the command line. This allows for modifying the command line state. | A Job representing the asynchronous operation. |
## CommandLineRead
The `CommandLineRead` interface provides read-only access to the command line state. It includes properties for accessing the current text, caret position, and active state of the command line.
### Properties
| Property | Type | Description |
|----------|------|-------------|
| `text` | `String` | The text currently displayed in the command line. |
| `caretPosition` | `Int` | The current position of the caret in the command line. |
| `isActive` | `Boolean` | True if the command line is currently active, false otherwise. |
## CommandLineTransaction
The `CommandLineTransaction` interface provides methods for modifying the command line state. It includes operations for setting text, inserting text, setting the caret position, and closing the command line.
### Methods
| Method | Description | Return Value |
|--------|-------------|--------------|
| `setText(text: String)` | Sets the text content of the command line. This replaces any existing text in the command line with the provided text. | None |
| `insertText(offset: Int, text: String)` | Inserts text at the specified position in the command line. | None |
| `setCaretPosition(position: Int)` | Sets the caret position in the command line. | None |
| `close(refocusEditor: Boolean = true): Boolean` | Closes the command line. If refocusEditor is true, the editor will be refocused after closing the command line. | True if the command line was closed, false if it was not active. |
# Tutorial: Creating an IdeaVim Plugin with the New API
> **⚠️ EXPERIMENTAL API WARNING**
>
> The Plugin API is currently in an **experimental stage** and is not yet recommended for production use.
>
> - The API is subject to breaking changes without notice
> - Features may be added, modified, or removed in future releases
> - Documentation may not fully reflect the current implementation
> - Use at your own risk for experimental purposes only
>
> We welcome feedback and bug reports to help improve the API, but please be aware that stability is not guaranteed at this time.
This tutorial will guide you through the process of creating a plugin for IdeaVim using the new API. We'll implement a "Replace with Register" plugin that allows you to replace text with the contents of a register.
## Table of Contents
- [Introduction](#introduction)
- [Prerequisites](#prerequisites)
- [Project Setup](#project-setup)
- [Plugin Structure](#plugin-structure)
- [Implementing the Plugin](#implementing-the-plugin)
- [Step 1: Create the init function](#step-1-create-the-init-function)
- [Step 4: Handle Different Selection Types](#step-4-handle-different-selection-types)
- [Testing Your Plugin](#testing-your-plugin)
## Introduction
The "Replace with Register" plugin ([link](https://github.com/vim-scripts/ReplaceWithRegister) to the original Vim plugin) demonstrates several important concepts in IdeaVim plugin development:
- Creating custom mappings for different Vim modes
- Working with registers
- Manipulating text in the editor
- Handling different types of selections (character-wise, line-wise, block-wise)
- Creating operator functions
This tutorial will walk you through each part of the implementation, explaining the concepts and techniques used.
## Project Setup
1. Clone the IdeaVim repo. (Todo: update)
## Plugin Structure
IdeaVim plugins using the new API are typically structured as follows:
1. An `init` function that sets up mappings and functionality
2. Helper functions that implement specific features
Let's look at how to implement each part of our "Replace with Register" plugin.
## Implementing the Plugin
### Step 1: Create the init function
First, create a Kotlin file for your plugin:
```kotlin
@VimPlugin(name="ReplaceWithRegister")
funVimApi.init(){
// We'll add mappings and functionality here
}
```
The `init` function has a responsibility to set up our plugin within the `VimApi`.
### Step 2: Define Mappings
Now, let's add mappings to our plugin. We'll define three mappings:
1.`gr` + motion: Replace the text covered by a motion with register contents
2.`grr`: Replace the current line with register contents
3.`gr` in visual mode: Replace the selected text with register contents
@@ -5,9 +5,9 @@ Using actions from external plugins is the same, as tracking and reusing any IDE
1. Install the plugin via Marketplace
2. Enable action tracking. You can enable it by one of the following ways:
* Execute `:set trackactionids` ex command or just `:set tai`
* Open the "Find actions" window by pressing `Ctrl-Shift-A` and search for "Track Action Ids" to find the toggle that enables and disables action tracking
* Open the "Find actions" window by pressing `Ctrl-Shift-A` and search for "Track Action IDs" to find the toggle that enables and disables action tracking
3. Execute the plugin action the way intended by plugin author "See Edit menu or use ⇧ + ⌥ + U / Shift + Alt + U" or just find the `Toggle Camel Case` action in the "Find actions" window (`Ctrl-Shift-A`). If you action tracking is on, you will see this notification in your right bottom corner:
- You can read a [post](https://github.com/JetBrains/ideavim/wiki/how-many-modes-does-vim-have) about how modes work in Vim and IdeaVim.
- Have you ever used `U` after `dd`? [Don't even try](https://github.com/vim/vim/blob/759d81549c1340185f0d92524c563bb37697ea88/src/ops.c#L874).
- A lot of variables that refer to visual mode start with two uppercase letters, e.g. `VIsual_active`. [Some examples](https://github.com/vim/vim/blob/master/src/normal.c#L17).
As mentioned [here](https://vi.stackexchange.com/a/42885/12441), this was done this way to avoid the clash with X11.
- Other [strange things](https://github.com/vim/vim/blob/759d81549c1340185f0d92524c563bb37697ea88/src/ex_docmd.c#L1845) from vi:
* ":3" jumps to line 3
* ":3|..." prints line 3
* ":|" prints current line
- Vim script doesn't skip white space before comma. `F(a ,b)` => E475.
- Fancy constants for [undolevels](https://vimhelp.org/options.txt.html#%27undolevels%27):
> The local value is set to -123456 when the global value is to be used.
- Vi (not Vim) is a POSIX standard, and [has a spec](https://pubs.opengroup.org/onlinepubs/9699919799/utilities/vi.html)! Vim is mostly POSIX compliant when Vi compatibility is selected with the `'compatible'` option, but there are still some differences that can be changed with `'copoptions'`. The spec is interesting because it documents the behaviour of different commands in a stricter style than the user documentation, describing the current line and column after the command, for example. [More details can be found by reading `:help posix`](https://vimhelp.org/vi_diff.txt.html#posix).
- The Vim documentation contains many easter eggs. We encounter them occasionally, but GitHub user mikesmithgh has compiled a substantial collection [here](https://github.com/mikesmithgh/vimpromptu).
- In addition to `:call err_teapot()`, which returns `E418: I'm a teapot`, there is also `:call err_teapot(1)`, which returns `E503: Coffee is currently not available`. Naturally, this is also supported in IdeaVim.
- Insert mode has all `Ctrl` keys mapped, except `Ctrl-B`. In the documentation, it is marked as **"CTRL-B in Insert
mode gone"**. Call `:h i_CTRL-B-gone` in Vim to read why `Ctrl-B` was removed.
Some files were not shown because too many files have changed in this diff
Show More
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.