Skip to content

editor: Fix line selection disappears when the cursor goes off screen - #60054

Merged
ChristopherBiscardi merged 5 commits into
zed-industries:mainfrom
feitreim:bugfix-line-selection-visibility
Jul 20, 2026
Merged

editor: Fix line selection disappears when the cursor goes off screen#60054
ChristopherBiscardi merged 5 commits into
zed-industries:mainfrom
feitreim:bugfix-line-selection-visibility

Conversation

@feitreim

Copy link
Copy Markdown
Collaborator

Objective

When you make a line selection in a row that is long enough to multiple lines long while wrapped the selection box will disappear when the cursor (or just origin of the line selection goes off screen).

Solution

The when you are in line selection mode the SelectionsCollection just enables line_mode, instead of storing the whole ranges of those line selections, so the Anchor of the selection is a single point. disjoint_in_range filters all the disjoint selections of the collection to fit inside some range, this is used to render the selection boxes on screen. disjoint_in_range had no special case for line_mode causing it to filter on the point instead of the bounds of the selection.

My fix is too add a case for line_mode that changes the filtering Anchor -> Point and then only filters on the row. Then use the proper bounds of the selection with that:

        let (start_ix, end_ix) = if self.line_mode {
            let start_row = range.start.to_point(buffer).row;
            let end_row = range.end.to_point(buffer).row;
            let start_ix = self
                .disjoint
                .partition_point(|probe| probe.end.to_point(buffer).row < start_row);
            let end_ix = self
                .disjoint
                .partition_point(|probe| probe.start.to_point(buffer).row <= end_row);
            (start_ix, end_ix)
        } else {
        ...

Testing

Before:

BEFORE.mov

After:

AFTER.mov

I also added some tests disjoint_in_range_line_mode_matches_whole_row, being the one that really tests this behavior. the other two are just for disjoint_in_range

I am using macOS 27.0 Beta (26A5368g), but I noticed this bug before upgrading.

Self-Review Checklist:

  • I've reviewed my own diff for quality, security, and reliability
  • Unsafe blocks (if any) have justifying comments
  • The content adheres to Zed's UI standards (UX/UI and icon guidelines)
  • Tests cover the new/changed behavior
  • Performance impact has been considered and is acceptable

Release Notes:

  • editor: Fixed bug where selection boxes wouldn't render when the cursor was offscreen.

@cla-bot cla-bot Bot added the cla-signed The user has signed the Contributor License Agreement label Jun 29, 2026
@zed-community-bot zed-community-bot Bot added the guild Pull requests by someone in Zed Guild. NOTE: the label application is automated via github actions label Jun 29, 2026

@ChristopherBiscardi ChristopherBiscardi left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the process for reproducing this? Does it rely on using a specific vim-mode sequence?

I can confirm this behavior when single-clicking into a soft-wrapped paragraph and scrolling the cursor out of view, but this is not the behavior that this PR targets, nor does it seem to be what is happening in the screen recordings.

@ChristopherBiscardi ChristopherBiscardi added the area:editor Feedback for code editing, formatting, editor iterations, etc label Jun 29, 2026
@feitreim

Copy link
Copy Markdown
Collaborator Author

yes you do need to be in vim mode to see the behavior I was originally describing so for replicating you can use the same setup you are using to see that single click behavior but then just be in vim mode and do shift+v to make a line selection.

The line_mode flag seems to have been made specifically for the modal editing modes. I looked into the behavior with the single-click you were describing, and despite that being from the active line highlighting system instead of selection stuff, its hitting the same disjoint_in_range function. the issue there is that this time line_mode isn't enabled, so it does the original behavior and fails to render. tldr: exactly the same issue, but theres no line_mode flag we can use to get around it.

the solution to that to me seems like to remove the line_mode special case and instead either
a) use the row based calculations I implemented for line_mode = true for all cases or
b) come up with some other clever way of doing the doing this that handles the wrapped lines or something.

I think a) makes the most sense, and maybe the solution is to make a new function, like disjoint_in_row_range or something to leave behavior unchanged for any other consumers. any input @ChristopherBiscardi ?

@feitreim

Copy link
Copy Markdown
Collaborator Author

refactored the line_mode branch to a different function, getting rid of the requirement for line_mode to actually be set. changed the call site in the element.rs to call this new function, fixing both the issue I noticed and the new issue you pointed out.

@ChristopherBiscardi ChristopherBiscardi left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks!

@ChristopherBiscardi
ChristopherBiscardi added this pull request to the merge queue Jul 20, 2026
Merged via the queue into zed-industries:main with commit bc61a30 Jul 20, 2026
35 checks passed
@ChristopherBiscardi ChristopherBiscardi self-assigned this Jul 20, 2026
jolutz pushed a commit to jolutz/zed that referenced this pull request Aug 8, 2026
…zed-industries#60054)

# Objective

When you make a line selection in a row that is long enough to multiple
lines long while wrapped the selection box will disappear when the
cursor (or just origin of the line selection goes off screen).


## Solution

The when you are in line selection mode the `SelectionsCollection` just
enables `line_mode`, instead of storing the whole ranges of those line
selections, so the Anchor of the selection is a single point.
`disjoint_in_range` filters all the disjoint selections of the
collection to fit inside some range, this is used to render the
selection boxes on screen. `disjoint_in_range` had no special case for
`line_mode` causing it to filter on the point instead of the bounds of
the selection.

My fix is too add a case for `line_mode` that changes the filtering
`Anchor` -> `Point` and then only filters on the row. Then use the
proper bounds of the selection with that:
```rust
        let (start_ix, end_ix) = if self.line_mode {
            let start_row = range.start.to_point(buffer).row;
            let end_row = range.end.to_point(buffer).row;
            let start_ix = self
                .disjoint
                .partition_point(|probe| probe.end.to_point(buffer).row < start_row);
            let end_ix = self
                .disjoint
                .partition_point(|probe| probe.start.to_point(buffer).row <= end_row);
            (start_ix, end_ix)
        } else {
        ...
```

## Testing

Before:


https://github.com/user-attachments/assets/a443de39-53d7-4ee4-b3af-d3be99ca1fcf

After:


https://github.com/user-attachments/assets/18c1c7ef-45cc-4dc8-9a8b-241252c376ab

I also added some tests `disjoint_in_range_line_mode_matches_whole_row`,
being the one that really tests this behavior. the other two are just
for `disjoint_in_range`

I am using macOS 27.0 Beta (26A5368g), but I noticed this bug before
upgrading.

## Self-Review Checklist:

- [x] I've reviewed my own diff for quality, security, and reliability
- [x] Unsafe blocks (if any) have justifying comments
- [x] The content adheres to Zed's UI standards
([UX/UI](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist)
and
[icon](https://github.com/zed-industries/zed/blob/main/crates/icons/README.md)
guidelines)
- [x] Tests cover the new/changed behavior
- [x] Performance impact has been considered and is acceptable

---

Release Notes:

- editor: Fixed bug where selection boxes wouldn't render when the
cursor was offscreen.
@zelenenka zelenenka removed the guild Pull requests by someone in Zed Guild. NOTE: the label application is automated via github actions label Aug 12, 2026
playdohface pushed a commit to playdohface/zed that referenced this pull request Aug 29, 2026
…zed-industries#60054)

# Objective

When you make a line selection in a row that is long enough to multiple
lines long while wrapped the selection box will disappear when the
cursor (or just origin of the line selection goes off screen).


## Solution

The when you are in line selection mode the `SelectionsCollection` just
enables `line_mode`, instead of storing the whole ranges of those line
selections, so the Anchor of the selection is a single point.
`disjoint_in_range` filters all the disjoint selections of the
collection to fit inside some range, this is used to render the
selection boxes on screen. `disjoint_in_range` had no special case for
`line_mode` causing it to filter on the point instead of the bounds of
the selection.

My fix is too add a case for `line_mode` that changes the filtering
`Anchor` -> `Point` and then only filters on the row. Then use the
proper bounds of the selection with that:
```rust
        let (start_ix, end_ix) = if self.line_mode {
            let start_row = range.start.to_point(buffer).row;
            let end_row = range.end.to_point(buffer).row;
            let start_ix = self
                .disjoint
                .partition_point(|probe| probe.end.to_point(buffer).row < start_row);
            let end_ix = self
                .disjoint
                .partition_point(|probe| probe.start.to_point(buffer).row <= end_row);
            (start_ix, end_ix)
        } else {
        ...
```

## Testing

Before:


https://github.com/user-attachments/assets/a443de39-53d7-4ee4-b3af-d3be99ca1fcf

After:


https://github.com/user-attachments/assets/18c1c7ef-45cc-4dc8-9a8b-241252c376ab

I also added some tests `disjoint_in_range_line_mode_matches_whole_row`,
being the one that really tests this behavior. the other two are just
for `disjoint_in_range`

I am using macOS 27.0 Beta (26A5368g), but I noticed this bug before
upgrading.

## Self-Review Checklist:

- [x] I've reviewed my own diff for quality, security, and reliability
- [x] Unsafe blocks (if any) have justifying comments
- [x] The content adheres to Zed's UI standards
([UX/UI](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist)
and
[icon](https://github.com/zed-industries/zed/blob/main/crates/icons/README.md)
guidelines)
- [x] Tests cover the new/changed behavior
- [x] Performance impact has been considered and is acceptable

---

Release Notes:

- editor: Fixed bug where selection boxes wouldn't render when the
cursor was offscreen.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:editor Feedback for code editing, formatting, editor iterations, etc cla-signed The user has signed the Contributor License Agreement

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants