Previously, `]m`/`[m` (method) and `]/`/`[/` (comment) motions would
navigate to incorrect positions when diff hunks were expanded. This was
caused by extracting raw `usize` values from `MultiBufferOffset` and
operating directly on the underlying buffer, which doesn't account for
expanded diff hunk content.
The fix properly uses `MultiBufferOffset` throughout and queries
`text_object_ranges` on the `MultiBufferSnapshot` instead of the
underlying buffer, ensuring correct coordinate mapping when diff content
is displayed inline.
Fixes#46612
Release Notes:
- Fixed vim method and comment navigation (`] m`, `[ m`, `] shift-m`, `[
shift-m`, `] /`, `[ /`) incorrectly positioning cursor when diff hunks
are expanded
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: dino <dinojoaocosta@gmail.com>
- Remove old attempt to sync scrolling
- Share a `ScrollAnchor` between the two sides, and be sure to resolve
it against the correct snapshot
- Allow either side to initiate an autoscroll request, and make sure
that request is processed in the same frame by the other side
Release Notes:
- N/A
---------
Co-authored-by: cameron <cameron.studdstreet@gmail.com>
Co-authored-by: Zed Zippy <234243425+zed-zippy[bot]@users.noreply.github.com>
Co-authored-by: Jakub <jakub@zed.dev>
Fixes#8048
## Summary
In vim mode, pressing Escape to dismiss the buffer search now correctly
restores the cursor to its original position, rather than leaving it at
the first match.
## Problem
When using vim's `/` command to search:
1. User positions cursor at line X
2. User presses `/` to open search, types a query
3. Matches are highlighted, cursor may visually jump to first match
4. User presses Escape to dismiss without navigating
5. **Bug:** Cursor ends up at first match instead of line X
This breaks vim parity where Escape should cancel the search and restore
cursor position.
## Solution
The fix leverages the `focused()` callback in `vim.rs`, which is called
when the editor regains focus after the search bar is dismissed.
**Key insight:** When search starts via `/`, the cursor position is
saved in `SearchState.prior_selections`. When search is *submitted* with
Enter, `search_submit()` drains these selections. But when search is
*dismissed* with Escape, they remain.
So in `focused()`, if:
- `prior_selections` is non-empty, AND
- The search bar's `is_dismissed()` returns true
...then we know the user dismissed the search (Escape) rather than
submitted it (Enter), and we restore the cursor.
## Why not handle `buffer_search::Dismiss` directly?
The initial approach tried to register a vim handler for the `Dismiss`
action. This didn't work because when Escape is pressed, the search bar
(which has focus) handles the `Cancel` action internally and calls its
`dismiss()` method directly—it doesn't dispatch `Dismiss` through the
action system. The vim handler registered on the editor was never
invoked.
## Test Plan
- Added `test_search_dismiss_restores_cursor` — verifies cursor
restoration when search is dismissed
- Added `test_search_dismiss_restores_cursor_no_matches` — verifies
behavior when query has no matches
- All 455 vim tests pass
- Manual testing confirms fix works with both `/` and `cmd-f`
## Release Notes
- Fixed vim mode: cursor now returns to original position when
dismissing buffer search with Escape (#8048)
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Thanks @injust for pointing this out. It seems a few other action docs
were also copied from elsewhere and weren't updated to reflect their
actual purpose. This PR fixes those, though I may not have covered all
of them.
Closes#46832
Release Notes:
- N/A
---------
Co-authored-by: Kunall Banerjee <hey@kimchiii.space>
Add support for Vim's `gdefault` option which makes the `:substitute`
command replace all matches in a line by default, instead of just the
first match. When enabled, the `/g` flag inverts this behavior.
- Add `vim.gdefault` setting
- Add `:set gdefault`, `:set nogdefault` (and short forms `:set gd`, `:set nogd`)
- Fix handling of multiple `/g` flags so that each one inverts the one before
Closes#36209
Release Notes:
- vim: Add `vim.gdefault` setting to make `/g` (replace all matches in a line) the default for substitutions, along with `:set gdefault` and `:set nogdefault` commands (short forms: `gd`, `nogd`)
---------
Co-authored-by: dino <dinojoaocosta@gmail.com>
The `}` and `{` paragraph motions now correctly treat only truly empty
lines (zero characters) as paragraph boundaries, matching vim's
documented behavior. Whitespace-only lines are no longer treated as
boundaries.
Changed `start_of_paragraph()` and `end_of_paragraph()` in
`editor/src/movement.rs` to check `line_len() == 0` instead of
`is_line_blank()`.
Note: This change does NOT affect the `ap`/`ip` text objects. Per vim's
`:help ap`, those DO treat whitespace-only lines as boundaries, which is
the existing (correct) behavior in `vim/src/object.rs`.
Closes#36171
Release Notes:
- Fixed vim mode paragraph motions (`}` and `{`) to correctly ignore
whitespace-only lines
---------
Co-authored-by: dino <dinojoaocosta@gmail.com>
Closes#46509
Release Notes:
- Fixed: `workspace::SendKeystrokes` would not allow remapping keys in
different keyboard layouts
---------
Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
Fixes the `vim::ScrollUp/Down` commands when a normal-mode vim or helix
selection exists.
Pretty straightforward: make the `scroll_editor` function aware of the
current vim/helix mode, and only expand the selection if a visual mode
is active.
Closes#47022
Release Notes:
- Fixed bug causing normal-mode vim/helix selections to get expanded
during `vim::Scroll` commands
---------
Co-authored-by: dino <dinojoaocosta@gmail.com>
Previously, when project search results first appeared, vim would
incorrectly switch to Visual mode. This happened because vim settings
(including `collapse_matches`) weren't synced to an editor until it
received focus. Since the results editor wasn't focused when the first
match was selected, the selection wasn't collapsed, causing vim to
interpret it as a visual selection.
Now vim settings are synced immediately when vim activates on an editor,
ensuring `collapse_matches` is set before any selections are made.
Closes#43878
Release Notes:
- Fixed vim mode incorrectly switching to Visual mode on first project
search
---------
Co-authored-by: dino <dinojoaocosta@gmail.com>
Closes #ISSUE
Moves the settings content definitions into their own crate, so that
they are compiled+cached separately from settings, primarily to avoid
recompiles due to changes in gpui. In that vain many gpui types such as
font weight/features, and `SharedString` were replaced in the content
crate, either with `*Content` types for font/modifier things, or
`String`/`Arc<str>` for `SharedString`. To make the conversions easy a
new trait method in the settings crate named `IntoGpui::into_gpui`
allows for `into()` like conversions to the gpui types in
`from_settings` impls.
Release Notes:
- N/A
---------
Co-authored-by: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com>
Fixes subword motion incorrectly jumping to next line when near end of
line. Updates boundary detection to use exclusive boundaries with
need_next_char parameter, matching regular word motion behavior.
Refactors word and subword motion to share boundary detection logic.
Closes#17780
Release Notes:
- Fixed subword motion incorrectly jumping to the next line when near
the end of a line
---------
Co-authored-by: dino <dinojoaocosta@gmail.com>
Fix a bug with `:norm` that disallowed trailing whitespace.
Commands that accept generic args (defined with `args()`) now preserve
trailing whitespace, while commands that accept filenames (defined with
`filename()`) have whitespace pre-trimmed. This allows, for example,
`:norm I ` to correctly insert spaces, matching NeoVim's behavior.
Release Notes:
- vim: Fixed `:norm` command to preserve trailing whitespace in
arguments (e.g., `:norm I ` now correctly inserts two spaces)
---------
Co-authored-by: dino <dinojoaocosta@gmail.com>
Add a `match_quotes` parameter to the `vim::Matching` action that
controls whether the `%` motion should treat quote characters (', ", `)
as matching pairs.
In Neovim, `%` only matches bracket pairs (([{}])), not quotes. Zed's
existing behavior includes quote matching, which some users prefer. To
preserve backwards compatibility while allowing users to opt into
Neovim's behavior, this PR:
1. Adds an optional `match_quotes` boolean parameter to the
`vim::Matching` action
2. Updates the default vim keymap to use ["vim::Matching", {
"match_quotes": true }], preserving Zed's current behavior
3. Users who prefer Neovim's behavior can rebind `%` in their keymap:
```
{
"context": "VimControl && !menu",
"bindings": {
"%": ["vim::Matching", { "match_quotes": false }]
}
}
```
When `match_quotes` is `false`, the `%` motion will skip over quote
characters and only match brackets/parentheses, matching Neovim's
default behavior.
Release Notes:
- vim: Added match_quotes parameter to the vim::Matching action to control
whether % matches quote characters
---------
Co-authored-by: dino <dinojoaocosta@gmail.com>
The default behavior for Vim search with `*` and `#` in normal mode is
to initiate a search and immediately jump to the next or previous match
respectively.
This behavior can be annoying, so Vim has many plugins to address this
specifically:
- [vim-asterisk](https://github.com/haya14busa/vim-asterisk)
-
[vim-SearchHighlighting](https://github.com/inkarkat/vim-SearchHighlighting)
- [vim-tranquille](https://github.com/RRethy/vim-tranquille)
This PR tries to emulate this behavior natively keeping up with Zed's
sane defaults and deviating from vanilla Vim when it makes sense.
Release Notes:
- Vim: `*` and `#` search doesn't jump immediately to next / previous
search.
When a recorded action moves focus away from the editor (e.g.,
`buffer_search::Deploy`), the `EndRepeat` action handler is not invoked
because is node is no longer on the dispatch path. This left
`dot_replaying` set to `true`, causing subsequent repeats to malfunction
and the `VimGlobals.pre_count` value to never be reset.
Reset `dot_replaying` as a fail-safe when the replayer exhausts its
action queue, ensuring the state is always cleaned up regardless of
whether `EndRepeat` was handled.
Release Notes:
- Fixed vim repeat (`.`) breaking when the recorded action moves focus
away from the editor
Co-authored-by: neel <neel@chot.ai>
Closes#45340
Release Notes:
- Fixed $ motion in vim mode to stay at end of the line when moving
vertically
---------
Co-authored-by: dino <dinojoaocosta@gmail.com>
Closes#36359
Release Notes:
- Fixes bug where `:g/pattern/norm commands` would only apply on the
last match
---------
Co-authored-by: dino <dinojoaocosta@gmail.com>
Draft as a base for continuing the discussion in #8008 : adds a
`SplitOperation` enum to support bindings like `["pane::SplitLeft",
{"operation": "Clear"}]`
To be discussed @MrSubidubi and others:
- Naming: Generally not happy with names yet and specifically `Empty` is
unclear, e.g., what does this mean for terminal panes? Added placeholder
code to split without cloning, but unsure what users would expect in
this case.
- ~~I removed `SplitAndMoveXyz` actions but I guess we should keep them
for backwards compatibility?~~
- May have missed details in the move implementation. Will check the
code again for opportunities to refactor more code after we agree on the
approach.
- ~~Tests should go to `crates/collab/src/tests/integration_tests.rs`?~~
Closes#8008
Release Notes:
- Add `pane::Split` mode (`{ClonePane,EmptyPane,MovePane}`) to allow
creating an empty buffer.
---------
Co-authored-by: Finn Evers <finn.evers@outlook.de>
Co-authored-by: MrSubidubi <finn@zed.dev>
This adds the following Vim commands:
- `:r[ead] [name]`
- `:{range}r[ead] [name]`
The most important parts of this feature are outlined
[here](https://vimhelp.org/insert.txt.html#%3Ar).
The only intentional difference between this and Vim is that Vim only
allows `:read` (no filename) for buffers with a file attached. I am
allowing it for all buffers because I think that could be useful.
Release Notes:
- vim: Added the [`:r[ead] [name]` Vim
command](https://vimhelp.org/insert.txt.html#:read)
---------
Co-authored-by: Ben Kunkle <ben@zed.dev>
## Summary
Addresses #16965
This PR adds support for **opening and saving** files with legacy
encodings (non-UTF-8).
Previously, Zed failed to open files encoded in Shift-JIS, EUC-JP, Big5,
etc., displaying a "Could not open file" error screen. This PR
implements automatic encoding detection upon opening and ensures the
original encoding is preserved when saving.
## Implementation Details
1. **Worktree (Loading)**:
* Updated `load_file` to use `chardetng` for automatic encoding
detection.
* Files are decoded to UTF-8 internal strings for editing, while
preserving the detected `Encoding` metadata.
2. **Language / Buffer**:
* Added an `encoding` field to the `Buffer` struct to store the detected
encoding.
3. **Worktree (Saving)**:
* Updated `write_file` to accept the stored encoding.
* **Performance Optimization**:
* **UTF-8 Path**: Uses the existing optimized `fs.save` (streaming
chunks directly from Rope), ensuring no performance regression for the
vast majority of files.
* **Legacy Encoding Path**: Implemented a fallback that converts the
Rope to a contiguous `String/Bytes` in memory, re-encodes it to the
target format (e.g., Shift-JIS), and writes it to disk.
* *Note*: This fallback involves memory allocation, but it is necessary
to support legacy encodings without refactoring the `fs` crate's
streaming interfaces.
## Changes
- `crates/worktree`:
- Add dependencies: `encoding_rs`, `chardetng`.
- Update `load_file` to detect encoding and decode content.
- Update `write_file` to handle re-encoding on save.
- `crates/language`: Add `encoding` field and accessors to `Buffer`.
- `crates/project`: Pass encoding information between Worktree and
Buffer.
- `crates/vim`: Update `:w` command to use the new `write_file`
signature.
## Verification
I validated this manually using a Rust script to generate test files
with various encodings.
**Results:**
* ✅ **Success (Opened & Saved correctly):**
* **Japanese:** `Shift-JIS` (CP932), `EUC-JP`, `ISO-2022-JP`
* **Chinese:** `Big5` (Traditional), `GBK/GB2312` (Simplified)
* **Western/Unicode:** `Windows-1252` (CP1252), `UTF-16LE`, `UTF-16BE`
* ⚠️ **limitations (Detection accuracy):**
* Some specific encodings like `KOI8-R` or generic `Latin1` (ISO-8859-1)
may partially display replacement characters (`?`) depending on the file
content length. This is a known limitation of the heuristic detection
library (`chardetng`) rather than the saving logic.
Release Notes:
- Added support for opening and saving files with legacy encodings
(Shift-JIS, Big5, etc.)
---------
Co-authored-by: CrazyboyQCD <53971641+CrazyboyQCD@users.noreply.github.com>
Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
Both `test_miniquotes_object` and `test_minibrackets_object` rely on
tree-sitter parsing for `MultiBufferSnapshot.bracket_ranges` to find
quote/bracket pairs. The `VimTestContext.set_state` call eventually
triggers async tree-sitter parsing, but `run_until_parked` doesn't
guarantee parsing completion.
We suspect this is what might be causing the flakiness on Windows, as
the syntax might not yet be parsed when the
`VimTestContext.simulate_keystrokes` call is made, so there's no bracket
pairs returned.
This commit adds an explicit await call on `Bufffer.parsing_idle` after
each `VimTestContext.set_state` call, to ensure tree-sitter parsing
completes before simulating keystrokes.
Release Notes:
- N/A
In a previous Pull Request, a new field was added to `editor::Editor`,
namely `cursor_offset_on_selection`, in order to control whether the
cursor representing the head of a selection should be positioned in the
last selected character, as we have on Vim mode, or after, like we have
when Vim mode is disabled.
This field would then be set by the `vim` crate, depending on the
current vim mode. However, it was noted that
`vim_mode_setting::VimModeSetting` already exsits and allows other
crates to determine whether Vim mode is enabled or not. Since we're
already checking `!range.is_empty()` in
`editor::element::SelectionLayout::new` we can then rely on simply
determining whether Vim mode is enabled to decide whether tho shift the
cursor one position to the left when making a selection.
As such, this commit removes the `cursor_offset_on_selection` field, as
well as any related methods in favor of a new `Editor.vim_mode_enabled`
method, which can be used to achieve the same behavior.
Relates to #42837
Release Notes:
- N/A
Vim visual mode and Helix selection mode both require the cursor to be
on the last character of the selection. Until now, this was implemented
by offsetting the cursor one character to the left whenever a block
cursor is used. (Since the visual modes use a block cursor.)
However, this oversees the problem that **some users might want to use
the block cursor without being in visual mode**. Meaning that the cursor
is offset by one character to the left even though Vim/Helix mode isn't
even activated.
Since the Vim mode implementation is separate from the `editor` crate
the solution is not as straightforward as just checking the current vim
mode. Therefore this PR introduces a new `Editor` struct field called
`cursor_offset_on_selection`. This field replaces the previous check
condition and is set to `true` whenever the Vim mode is changed to a
visual mode, and `false` otherwise.
Closes#36677 and #20121
Release Notes:
- Fixes block and hollow cursor being offset when selecting text
---------
Co-authored-by: dino <dinojoaocosta@gmail.com>
Closes#21324
Adds four new commands:
- `markdown::MoveUp`, `markdown::MoveDown` - these scroll up and down in
markdown preview mode, by no more than the height of a large headline.
- `markdown::MoveUpByItem`, and `markdown::MoveDownByItem` - these
scroll up and down by the height of the item at the top of the markdown
preview window. So headlines and large codeblocks, for instance, scroll
further than individual paragraph lines.
Also attempts to create sensible defaults:
`down` -> `markdown::ScrollDown`
`up` -> `markdown::ScrollUp`
`alt-down` -> `markdown::ScrollDownByItem`
`alt-up` -> `markdown::ScrollUpByItem`
And in Vim:
`ctrl-u` -> `markdown::ScrollPageUp`
`ctrl-d` -> `markdown::ScrollPageDown`
`ctrl-e` -> `markdown::ScrollDown`
`ctrl-y` -> `markdown::ScrollUp`
Release Notes:
- Added commands `markdown::ScrollUp`, `markdown::ScrollDown`,
`markdown::ScrollUpByItem`, and `markdown::ScrollDownByItem`
- Changed commands `markdown::MovePageUp` to `markdown::ScrollPageUp`
and `markdown::MovePageDown` to `markdown::ScrollPageDown`
Fixes a few cases where the commit view would layout shift as the diff
loaded. This was caused by:
- Adding the commit message buffer after all the diff files
- Using the gutter dimensions from the last frame for the avatar spacing
Release Notes:
- commit view: Fix layout shift while loading commit
---------
Co-authored-by: MrSubidubi <dev@bahn.sh>
Implements a specialized constructor `LanguageName::new_static` for
`&'static str` which reduces allocations.
`LanguageName::new` always backs the underlying `SharedString` with an
owned `Arc<str>` even when a `&'static str` is passed. This makes us
allocate each time we create a new `LanguageName` no matter what.
Creating a specialized constructor for `&'static str` allows us to
essentially construct them for free.
Additional change:
Encourages using explicit constructors to avoid needless allocations.
Currently there were no instances of this trait being called where the
lifetime was not `'static` saving another 48 locations of allocation.
```rust
impl<'a> From<&'a str> for LanguageName {
fn from(str: &'a str) -> Self {
Self(SharedString::new(str))
}
}
// to
impl From<&'static str> for LanguageName {
fn from(str: &'static str) -> Self {
Self(SharedString::new_static(str))
}
}
```
Release Notes:
- N/A