We don’t know the background color behind a non-opaque editor, so we
should skip contrast correction in that case. This prevents
single-editor mode (which is always transparent) from showing weird text
colors when text is selected.
We can’t account for the actual background during contrast correction
because we compute contrast outside gpui, while the actual color
blending happens inside gpui during drawing.
<img width="522" height="145" alt="image"
src="https://github.com/user-attachments/assets/6ee71475-f666-482d-87e6-15cf4c4fceef"
/>
Release Notes:
- Fixed an issue where Command Palette text looked faded when selected.
Closes#35787Closes#17890Closes#28789Closes#36495
How it works:
For highlights (and selections) within the visible rows of the editor,
we split them row by row. This is efficient since the number of visible
rows is constant. For each row, all highlights and selections, which may
overlap, are flattened using a line sweep. This produces non-overlapping
consecutive segments for each row, each with a blended background color.
Next, for each row, we split text runs into smaller runs to adjust its
color using APCA contrast. Since both text runs and segment are
non-overlapping and consecutive, we can use two-pointer on them to do
this.
For example, a text run for the variable red might be split into two
runs if a highlight partially covers it. As a result, one part may
appear as red, while the other appears as a lighter red, depending on
the background behind it.
Result:
<img width="1458" height="949" alt="image"
src="https://github.com/user-attachments/assets/4814c93d-12e7-4b4d-8542-d912acccfb8e"
/>
<img width="1459" height="952" alt="image"
src="https://github.com/user-attachments/assets/9e497b6c-3e66-43e8-8e5b-f634dd5ee8d3"
/>
<img width="1457" height="621" alt="image"
src="https://github.com/user-attachments/assets/8dfa6ce5-f46b-45b9-8008-66169d5aecd4"
/>
Release Notes:
- Improved text contrast when selected or highlighted in the editor.
This PR adds the ability for a user to select one or more blocks of text
and wrap each selection in an HTML tag — which works by placing multiple
cursors inside the open and close tags so the appropriate element name
can be typed in to all places simultaneously.
This is similar to the emmet "Wrap with Abbreviation" functionality
discussed in #15588 but is a simpler version that does not rely on
Emmet's language server.
Here's a preview of the feature in action:
https://github.com/user-attachments/assets/1931e717-136c-4766-a585-e4ba939d9adf
Some notes and questions:
- The current implementation is a hardcoded with regards to supported
languages. I'd love some direction on how much of this information to
push into the relevant language structs.
- I can see this feature as something that languages added by an
extension would want to enable support for — is this something you'd
want?
- The syntax is hardcoded to support HTML/XML/JSX-like languages. I
don't suppose this is a problem but figured I'd point it out anyway.
- I called it "Wrap in tag" but open to whatever naming you feel is
appropriate.
- The implementation doesn't use `manipulate_lines` — I wasn't sure how
make use of that without extra overhead / bookkeeping — does this seem
fine?
- I could also investigate adding wrap in abbreviation support by
communicating with the Emmet language server but I think I'll need some
direction on how to handle Emmet's custom LSP message. I could do this
either in addition to or instead of this feature — though imo this
feature is a nice "shortcut" regardless.
Release Notes:
- Added a new "Wrap Selections in Tag" action that lets you wrap one or
more selections in tags based on language. Works in HTML, JSX, and
similar languages, and places cursors inside both opening and closing
tags so you can type the tag name once and apply it everywhere.
---------
Co-authored-by: Smit Barmase <heysmitbarmase@gmail.com>
Closes#36300
This PR follows Windows conventions by introducing
`KeybindingKeystroke`, so shortcuts now show up as `ctrl-shift-4`
instead of `ctrl-$`.
It also fixes issues with keyboard layouts: when `use_key_equivalents`
is set to true, keys are remapped based on their virtual key codes. For
example, `ctrl-\` on a standard English layout will be mapped to
`ctrl-ё` on a Russian layout.
Release Notes:
- N/A
---------
Co-authored-by: Kate <kate@zed.dev>
<img width="1988" height="1420" alt="multi-buffer-icons-git-diff"
src="https://github.com/user-attachments/assets/48f9722f-ca09-4aa7-ad7a-0b7e85f440d9"
/>
Unfortunately, `cargo format` decided to reformat everything. Probably,
because of hitting the right margin, no idea. The essence of this change
is the following:
```rust
.map(|path_header| {
let filename = filename
.map(SharedString::from)
.unwrap_or_else(|| "untitled".into());
let path = path::Path::new(filename.as_str());
let icon =
FileIcons::get_icon(path, cx).unwrap_or_default();
let icon = Icon::from_path(icon).color(Color::Muted);
let label = Label::new(filename).single_line().when_some(
file_status,
|el, status| {
el.color(if status.is_conflicted() {
Color::Conflict
} else if status.is_modified() {
Color::Modified
} else if status.is_deleted() {
Color::Disabled
} else {
Color::Created
})
.when(status.is_deleted(), |el| el.strikethrough())
},
);
path_header.child(icon).child(label)
})
```
Release Notes:
- Added file icons to multi buffer view
This removes around 900 unnecessary clones, ranging from cloning a few
ints all the way to large data structures and images.
A lot of these were fixed using `cargo clippy --fix --workspace
--all-targets`, however it often breaks other lints and needs to be run
again. This was then followed up with some manual fixing.
I understand this is a large diff, but all the changes are pretty
trivial. Rust is doing some heavy lifting here for us. Once I get it up
to speed with main, I'd appreciate this getting merged rather sooner
than later.
Release Notes:
- N/A
Prior to https://github.com/zed-industries/zed/pull/34564 as well as
https://github.com/zed-industries/zed/pull/26893, we would have cases
where editors would be scrollable even if `soft_wrap` was set to
`editor_width`. This has regressed and improved quite a few times back
and forth. The issue was only within the editor code, the code for the
wrap map was functioning and tested properly.
Hence, this PR adds two tests to the editor rendering code in an effort
to ensure that we maintain the current correct behavior.
Release Notes:
- N/A
This adds a context menu to buffer headers mimicking that of pane tabs,
notably being able to copy the relative and absolute paths of the buffer
as well as opening a terminal in the parent.
Confusingly prior to this right clicking a buffer header used to open
the context menu of the underlying editor.
Release Notes:
- Added context menu for buffer titles
This PR improves the toggle button group to be more responsive across
different layouts. This is accomplished by ensuring each button takes up
the same amount of space in the parent containers layout.
Ideally, this should be done with grids instead of a flexbox container,
as this would be much better suited for this purpose. Yet, since we lack
support for this, we go with this route for now.
| Before | After |
| --- | --- |
| <img width="1608" height="1094" alt="Bildschirmfoto 2025-08-13 um 11
24 26"
src="https://github.com/user-attachments/assets/2a4b5a59-6483-4f79-8fcb-e26e22071795"
/> | <img width="1608" height="1094" alt="Bildschirmfoto 2025-08-13 um
11 29 36"
src="https://github.com/user-attachments/assets/e6402729-6a8f-4a44-b79e-a569406edfff"
/> |
Release Notes:
- N/A
Just like with diagnostics, adding a configurable padding to inline
blame
Release Notes:
- Added configurable padding to inline blame
---------
Co-authored-by: Cole Miller <cole@zed.dev>
Co-authored-by: Peter Tripp <petertripp@gmail.com>
Closes#34881
For horizontal scroll, we weren't keeping track of the `local` bool, so
whenever the agent tries to autoscroll horizontally, it would be seen as
a user scroll event resulting in unfollow.
Release Notes:
- Fixed an issue where the Follow Agent could unexpectedly stop
following during edits.
Remake of #8967
> Hey there,
>
> I have started relying on this action, that I've also put into VSCode
as [an extension](https://github.com/Gregoor/soy). On some level I don't
know how people code (cope?) without it:
>
> Release Notes:
>
> * Added UnwrapSyntaxNode action
>
>
https://github.com/zed-industries/zed/assets/4051932/d74c98c0-96d8-4075-9b63-cea55bea42f6
>
> Since I had to put it into Zed anyway to make it my daily driver, I
thought I'd also check here if there's an interest in shipping it by
default (that would ofc also personally make my life better, not having
to maintain my personal fork and all).
>
> If there is interest, I'd be happy to make any changes to make this
more mergeable. Two TODOs on my mind are:
>
> * unwrap multiple into single (e.g. `fn(≤a≥, b)` to `fn(≤a≥)`)
> * multi-cursor
> * syntax awareness, i.e. only unwrap if it does not break syntax (I
added [a coarse version of that for my VSC
extension](https://github.com/Gregoor/soy/blob/main/src/actions/unwrap.ts#L29))
>
> Somewhat off-topic: I was happy to see that you're
[also](https://github.com/Gregoor/soy/blob/main/src/actions/unwrap.test.ts)
using rare special chars in test code to denote cursor positions.
Release Notes:
- Added UnwrapSyntaxNode action
---------
Co-authored-by: Peter Tripp <peter@zed.dev>
This PR adds an `editor: convert to sentence case` action.
I frequently find myself copying branch names and then removing the
hyphens and ensuring the first letter is capitalized, and then using the
result text for the commit message.
For example:
<img width="927" height="482" alt="image"
src="https://github.com/user-attachments/assets/adf14a37-a92e-44df-8c0e-267b5c7677fb"
/>
You can achieve this with a combination of other text manipulation
commands, but this action makes it even easier.
Also, moved `toggle_case` down into the area where all other commands
internally using `manipulate_text` are located.
Release Notes:
- Added `editor: convert to sentence case`
https://github.com/user-attachments/assets/d472fbdd-7736-4bd7-8a90-8cca356b2815
This PR adds `editor: diff clipboard with selection` - good for spotting
the differences in eerily-similar code, which is when refactoring code,
as you need to see what needs to be passed in in order to maintain
previous behavior of both snippets.
1. Copy some text from anywhere
2. Highlight some text in Zed
3. Run `editor: diff clipboard with selection`
Like JetBrains' IDEs and VS Code with the `PartialDiff` package, if the
selection is empty, we take the entire buffer as the selection.
Caveats:
- We do not know the language of the text in the clipboard. I went ahead
and just assumed that in most cases, it will be the same language as the
selected text, which does mean we will highlight the old text
incorrectly if they are copying from a different language, but I think
in most cases, it will be the same, and the alternative of always having
no syntax highlighting is worse. PyCharm seems to do the same thing.
Release Notes:
- Added an `editor: diff clipboard with selection` action
---------
Co-authored-by: Junkui Zhang <364772080@qq.com>
Co-authored-by: Ben Kunkle <ben@zed.dev>
Make the git blame popover available via the keymap by making it an
action. The blame popover stays open after being shown via the action,
similar to the `editor::Hover` action.
I added a default vim-mode key binding for `g b`, which goes in hand
with `g h` for hover. I'm not sure what the keybind would be for regular
layouts, if any would be set by default.
I'm opening this as a draft because I coludn't figure out a way to
position the popover correctly above/under the cursor head. I saw some
uses of `content_origin` in other places for calculating absolute pixel
positions, but I'm not sure how to make use of it here without doing a
big refactor of the blame popover code 🤔. I would appreciate some
help/tips with positioning, because it seems like the last thing to
implement here.
Opening as a draft for now because I think without the correct
positioning this feature is not complete.
Closes https://github.com/zed-industries/zed/discussions/26447
Release Notes:
- Added `editor::BlameHover` action for showing the git blame popover
under the cursor. By default bound to `ctrl-k ctrl-b` and to `g h` in
vim mode.
Follow-up for https://github.com/zed-industries/zed/pull/34557
This PR clears the selection drag state on click, because mouse up
doesn't trigger on click event because of `cx.stop_propagation`. The
issue occurs with similar repro steps as mentioned in the attached PR.
Release Notes:
- Fixed the issue where the green (+) cursor style sometimes appears
when navigating to the definition in buffer.
Closes#34044
`EditorMode::SingleLine { auto_width: true }` was only used for the
title editor in the rules library, and following
https://github.com/zed-industries/zed/pull/31994 we can replace that
with a normal single-line editor without problems. The auto-width editor
was interacting badly with the recently-added newline visualization
code, causing a panic during layout---by switching it to
`Editor::single_line` the newline visualization works there too.
Release Notes:
- Fixed a panic that could occur when opening the rules library.
---------
Co-authored-by: Finn <finn@zed.dev>
This PR aims to improve the minimap performace. This is primarily
achieved by disabling/removing stuff that is not shown in the minimal as
well as by assuring the display map is not updated during minimap
prepaint.
This should already be much better in parts, as the block map as well as
the fold map will be less frequently updated due to the minimap
prepainting (optimally, they should never be, but I think we're not
quite there yet).
For this, I had to remove block rendering support for the minimap, which
is not as bad as it sounds: Practically, we were currently not rendering
most blocks anyway, there were issues due to this (e.g. scrolling any
visible block offscreen in the main editor causes scroll jumps
currently) and in the long run, the minimap will most likely need its
own block map or a different approach anyway. The existing
implementation caused resizes to occur very frequently for practically
no benefit. Can pull this out into a separate PR if requested, most
likely makes the other changes here easier to discuss.
This is WIP as we are still hitting some code path here we definitely
should not be hitting. E.g. there seems to be a rerender roughly every
second if the window is unfocused but visible which does not happen when
the minimap is disabled.
While this primarily focuses on the minimap, it also touches a few other
small parts not related to the minimap where I noticed we were doing too
much stuff during prepaint. Happy for any feedback there aswell.
Putting this up here already so we have a place to discuss the changes
early if needed.
Release Notes:
- Improved performance with the minimap enabled.
- Fixed an issue where interacting with blocks in the editor would
sometimes not properly work with the minimap enabled.
In multibuffers adds the ability to alt-click to fold/unfold all
excepts. In singleton buffers it adds the ability to toggle back and
forth between `editor::FoldAll` and `editor::UnfoldAll`.
Bind it in your keymap with:
```json
{
"context": "Editor && (mode == full || multibuffer)",
"bindings": {
"cmd-k cmd-o": "editor::ToggleFoldAll"
}
},
```
<img width="253" height="99" alt="Screenshot 2025-07-11 at 17 04 25"
src="https://github.com/user-attachments/assets/94de8275-d2ee-4cf8-a46c-a698ccdb60e3"
/>
Release Notes:
- Add ability to fold all excerpts in a multibuffer (alt-click) and in
singleton buffers `editor::ToggleFoldAll`
We not do not create new snapshots anymore when autoscrolling
horizontally and also do not notify any longer should the new scroll
position match the old one.
Release Notes:
- N/A
---------
Co-authored-by: Michael Sloan <mgsloan@gmail.com>
* Experimentally `scroll_manager.anchor()` appears to be the same before
and after this 2nd call of `autoscroll_horizontally`
* Nothing these depend on seem to be mutated between the calls (and
since this is prepaint, stuff within editor also shouldn't be mutated)
Release Notes:
- N/A
Co-authored-by: Finn <finn@zed.dev>
When [`drag_and_drop_selection` is
true](https://zed.dev/docs/configuring-zed#drag-and-drop-selection),
users can make a selection in the buffer and then drag and drop it to a
new location. However, the editor forces users to wait 300ms after mouse
down before dragging. If users try to drag before this delay has
elapsed, they will create a new text selection instead, which can create
the impression that drag and drop does not work.
I made two changes to improve the UX of this feature:
* If users do not want a delay before drag and drop is enabled, they can
set the `drag_and_drop_selection.delay_ms` setting to 0.
* If the user has done a mouse down on a text selection, the cursor
changes to a copy affordance as soon as the configured delay has
elapsed, rather than waiting for them to start dragging. This way they
don't need to guess at when the delay has elapsed.
The default settings for this feature are now:
```
"drag_and_drop_selection": {
"enabled": true,
"delay_ms": 300
}
```
Closes#33915
Before:
https://github.com/user-attachments/assets/7b2f986f-9c67-4b2b-a10e-757c3e9c934b
After:
https://github.com/user-attachments/assets/726d0dbf-e58b-41ad-93d2-1a758640b422
Release Notes:
- Migrate `drag_and_drop_selection` setting to
`drag_and_drop_selection.enabled`.
- Add `drag_and_drop_selection.delay_ms` setting to configure the delay
that must elapse before drag and drop is allowed.
- Show a ready to drag cursor affordance as soon as the delay has
elapsed
---------
Co-authored-by: Smit Barmase <heysmitbarmase@gmail.com>
This Pull Request introduces various changes to the editor's horizontal
scrolling, mostly focused on vim mode's horizontal scroll motions (`z
l`, `z h`, `z shift-l`, `z shift-h`). In order to make it easier to
review, the logical changes have been split into different sections.
## Cursor Position Update
Changes introduced on https://github.com/zed-industries/zed/pull/32558
added both `z l` and `z h` to vim mode but it only scrolled the editor's
content, without changing the cursor position. This doesn't reflect the
actual behavior of those motions in vim, so these two commits tackled
that, ensuring that the cursor position is updated, only when the cursor
is on the left or right edges of the editor:
-
https://github.com/zed-industries/zed/commit/ea3b866a763ba0bcfc12999ee1741c6528c895b7
-
https://github.com/zed-industries/zed/commit/805f41a913c6e86ef8be550d363a3cc2caeccbe9
## Horizontal Autoscroll Fix
After introducing the cursor position update to both `z l` and `z h` it
was noted that there was a bug with using `z l`, followed by `0` and
then `z l` again, as on the second use `z l` the cursor would not be
updated. This would only happen on the first line in the editor, and it
was concluded that it was because the
`editor::scroll::autoscroll::Editor.autoscroll_horizontally` method was
directly updating the scroll manager's anchor offset, instead of using
the `editor::scroll::Editor.set_scroll_position_internal` method, like
is being done by the vertical autoscroll
(`editor::scroll::autoscroll::Editor.autoscroll_vertically`).
This wouldn't update the scroll manager's anchor, which would still
think it was at `(0, 1)` so the cursor position would not be updated.
The changes in [this
commit](https://github.com/zed-industries/zed/commit/3957f02e189018ef559cd28516f0b872026b0ce1)
updated the horizontal autoscrolling method to also leverage
`set_scroll_position_internal`.
## Visible Column Count & Page Width Scroll Amount
The changes in
https://github.com/zed-industries/zed/commit/d83652c3ae1d0356fd7dca7b8922a0116de39ff0
add a `visible_column_count` field to `editor::scroll::ScrollManager`
struct, which allowed the introduction of the `ScrollAmount::PageWidth`
enum.
With these changes, two new actions are introduced,
`vim::normal::scroll::HalfPageRight` and
`vim::normal::scroll::HalfPageLeft` (in
https://github.com/zed-industries/zed/commit/7f344304d56337654a34b1b461a1dc69defd2e4e),
which move the editor half page to the right and half page to the left,
as well as the cursor position, which have also been mapped to `z
shift-l` and `z shift-h`, respectively.
Closes#17219
Release Notes:
- Improved `z l` and `z h` to actually move the cursor position, similar
to vim's behavior
- Added `z shift-l` and `z shift-h` to scroll half of the page width's
to the right or to the left, respectively
---------
Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
Closes#18263
Improvements:
• **Batch text rendering** - Combine adjacent cells with identical
styling into single text runs to reduce draw calls
• **Throttle hyperlink searches** - Limit hyperlink detection to every
100ms or when mouse moves >5px to reduce CPU usage
• **Pre-allocate collections** - Use `Vec::with_capacity()` for cells,
runs, and regions to minimize reallocations
• **Optimize background regions** - Merge adjacent background rectangles
to reduce number of draw operations
• **Cache selection text** - Only compute terminal selection string when
selection exists
Release Notes:
- Improved terminal rendering performance.
---------
Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
Reapplies the fix from #33514 which was removed in #33554. Wrap guides
are currently drifting again due to this on main.
Slightly changed the approach here so that we now actually only save the
wrap guides in the `EditorLayout` that will actually be painted. Also
ensures that we paint indent guides that were previously hidden behind
the vertical scrollbar once it auto-hides.
I wanted to add tests for this, however, I am rather sure this depends
on the work/fixes in #33590 and thus I'd prefer to add these later so we
can have this fix in the next release.
Release Notes:
- N/A
In #32656 I generalized the argument to change selections to allow
controling both the scroll and the nav history (and the completion
trigger).
To avoid conflicting with ongoing debugger cherry-picks I left the
argument as an `impl Into<>`, but I think it's clearer to make callers
specify what they want here.
I converted a lot of `None` arguments to `SelectionEffects::no_scroll()`
to be exactly compatible; but I think many people used none as an "i
don't care" value in which case Default::default() might be more
appropraite
Closes #ISSUE
Release Notes:
- N/A