# helix: Add \`m i m\`/\`m a m\` and preserve the selection after those actions \(\#59201\) · gitcafe/zed

[View on GitCafe](https://git.cafe/gitcafe/zed/commit/756c2b737cdad3bb439966e5c43e25d68275d661)

Repository: [gitcafe/zed](https://git.cafe/gitcafe/zed)

Visibility: public

Requested revision: 756c2b737cdad3bb439966e5c43e25d68275d661

Requested commit: 756c2b737cdad3bb439966e5c43e25d68275d661

Commit: 756c2b737cdad3bb439966e5c43e25d68275d661

Tree: 0cdedbd24e49f5eb9b943da896f459cbc82d7994

Author: Mikhail Pertsev

Committer: GitHub

## Message

```
helix: Add `m i m`/`m a m` and preserve the selection after those actions (#59201)

# Objective

- Preserve Helix mode selections after surround-related commands such as
adding, deleting, and replacing surrounds.
- Add Helix-compatible closest-pair behavior for `m i m` / `m a m` and
surround commands that target `m`.
- Avoid long stalls in Helix text-object boundary scans by removing
repeated syntax-scope classification during textual scans.
- Keep workflows like selecting text with `m i w`, applying `m s`, and
continuing with `i`, `a`, or another surround command aligned with Helix
behavior.

## Solution

- Add selection anchors that preserve each pre-edit selection range and
direction through surround edits.
- Update Helix surround add so the resulting selection includes the
added delimiters, allowing surround commands to compose and
insert/append actions to land outside the pair.
- Apply preserved selections to Helix surround delete and replace so
non-empty selections are not collapsed to the cursor after the edit.
- Add Helix-specific text-object bindings so `m` selects the closest
surrounding pair, `t` selects types/classes, `c` selects comments, and
`x` selects XML/HTML elements in Helix mode.
- Match the current closest pair through language bracket queries, so
delimiters inside strings or comments do not count as surrounding pairs.
- Resolve the textual boundary scanner's `CharClassifier` once per scan
and pass it by reference instead of recomputing the same classifier for
every scanned character. Word and subword boundaries still clone it only
to apply the `ignore_punctuation` flag.

## Performance

The textual boundary scanner previously called
`char_classifier_at(from.0)` inside the per-character comparison
closures for the four `ImmediateBoundary` scan methods and
`FuzzyBoundary::to_boundary`. That lookup resolves the language scope
through the syntax layers, and the value was loop-invariant because
every iteration used the scan's starting offset.

Local debug benchmark, using `] m` in a roughly 540 KB buffer with no
pair characters so the scan runs to EOF across all pair boundaries:

|        | `] m` |
| ------ | -----: |
| Before | 36.3 s |
| After  | 210 ms |

This speeds up the remaining textual scanner paths, including `m i (` /
`m i "`-style pair objects, word/sentence/paragraph objects, `]` / `[`
text-object navigation, and `] m` / `[ m`. The current-pair `m i m`, `m
a m`, `m d m`, and `m r m` paths use the tree-sitter closest-pair path
instead.

## Testing

- Ran `cargo test -p vim test_helix_surround -- --nocapture`.
- The focused Helix surround test filter passed: 10 passed, 0 failed.
- Added coverage for closest-pair selection, Helix-specific text-object
keys, and surround selection preservation.

## 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:

- Added Helix mode support for closest-pair text objects with `m i m`
and `m a m`.
- Fixed Helix mode surround commands to preserve selections after edits
and avoid slow textual boundary scans.
```

## Parents

- [8166e3d7b8b42d8aaf4d4dee7fcd25ab4ec65105](https://git.cafe/gitcafe/zed/commit/8166e3d7b8b42d8aaf4d4dee7fcd25ab4ec65105?format=markdown)

[Source at this commit](https://git.cafe/gitcafe/zed/tree/756c2b737cdad3bb439966e5c43e25d68275d661?format=markdown)
