Nothing kept the vimLastHighlighters list and the markup model in step, and nothing could answer which part of the document is currently highlighted - both of which scoping the highlights to the viewport needs.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`y<C-V>{motion}` (`:help o_CTRL-V`) makes `getMotionRange` return a
rectangular block, i.e. a multi-range `TextRange` with one range per row
of the block. `YankGroupBase.yankMotion` assumed a single range and
tripped `assert(motionRange.size() == 1)`; with assertions disabled it
silently yanked only the first row of the block, characterwise.
Handle a blockwise-forced motion the way the delete path already does:
store the whole multi-range as a blockwise yank and skip the
characterwise-to-linewise promotion, which does not apply to a block.
Found by the YankDeletePropertyTest property test.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`c_CTRL-R_CTRL-F` uses the *exclusive* end offset of the current incsearch
match as the document offset to look for a filename at. When the match ends
at the very end of the document, that offset equals the text length, and
`findFilenameAtOrFollowingCursor` indexed the text with it directly.
Guard against an offset that isn't a valid index into the text, and return
null, which reports E446 "No file name under cursor". This is the same guard
that `findWordAtOrFollowingCursor` already has, and it matches Vim, which
finds no identifier or filename when the position is past the end of the line.
Found by the property based tests.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Expand % signs also during command execution, not only with tab
completion.
% is the buffer name, relative while the file is under a content root;
only :p forces the full path. Expansion runs in a single forward pass
that honours \% and never rescans the expanded name, so a file name
containing % no longer loops forever. :! expands only the file-name
specials and leaves $VAR and ~ to the shell, as Vim does.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`insertText` declared a local `offset` that shadowed its own `offset`
parameter, leaving the parameter unused and the body reaching for
`commandLine.caret.offset` instead. The two are the same value (that is what
the base class passes), so this is not a behaviour change — but the shadowing
hides the parameter and makes the function harder to read.
Name the local after what it holds, and use the parameter.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`findPreviousWordOne` always steps back one character before it starts
skipping, to avoid getting stuck on the start of a word. When the caller
passes `allowMoveFromWordStart = false`, the character at the new position is
read to decide whether to skip at all — but if the search started at offset 0,
that position is -1 and the read throws.
The only caller that passes `allowMoveFromWordStart = false` is
`findWordObject` when expanding a right-to-left visual selection, so `viw`,
`vaw`, `viW` and `vaW` crashed whenever the selection reached the very start of
the file, e.g. `v h iw` with the caret on the second character.
Return 0 as soon as we step past the start of the text. Every path through the
rest of the function already returns 0 in that case, so this only affects the
out of bounds read.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`g8` is documented as printing "the hex values of the bytes used in the
character under the cursor, assuming it is in UTF-8 encoding". IdeaVim
printed the hex value of a single UTF-16 code unit instead, so anything
outside ASCII was wrong: `é` reported "e9" rather than "c3 a9", and a
character outside the BMP reported half of its surrogate pair ("d83d")
rather than its four UTF-8 bytes.
Encode the full code point at the caret to UTF-8 and format each byte as
two lowercase hex digits, space separated. ASCII output is unchanged.
Vim also appends the bytes of trailing composing characters, separated
by `+`. That is still not implemented, and is noted in the KDoc.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`g8` read `editor.text()[caret.offset]` unguarded. On an empty file, or
with the caret on an empty last line, the offset is the end of the text
and the read threw IndexOutOfBoundsException.
Vim's cursor never sits on the line break - at the end of a line it sits
on the line's terminating NUL - so there is no character under it and
Vim reports "NUL". Do the same, which covers both the crashing offsets
and an empty line in the middle of the file (which previously reported
the hex value of the line feed, "a").
Adds FileGetHexActionTest, which had no coverage at all.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`gx` opens the URL under the caret with an external program and never moves
the caret - the existing `test gx opens the URL without moving the caret`
asserts exactly that. Saving a jump location was therefore pointless and
actively harmful:
- `addJump(reset = true)` pushed the *unchanged* caret position onto the jump
list and reset the jump spot, so a `<C-O>` right after `gx` landed on the
caret's own line instead of returning to the previous jump.
- `saveJumpLocation` also overwrites the `'` mark, breaking `''`.
- The action is a `ForEachCaret` handler, so both happened once per caret.
Vim does not list `gx` under `:help jump-motions`, and netrw's `gx`
implementation leaves the jump list alone.
Also drop the `FLAG_SAVE_JUMP` flag: it is only honoured by
`MotionActionHandler`, so on a `VimActionHandler` it was inert and misleading.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>