MX48@LiveandConrad Irwin b5827ede3a Fix terminal combining marks (#53176)
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 is consistent with the [UI/UX
checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist)
- [x] Tests cover the new/changed behavior
- [x] Performance impact has been considered and is acceptable

Closes #46942

## Problem

The terminal's monospace grid forcing logic in `LineLayoutCache` breaks
rendering of combining marks in complex scripts. Thai, Lao, Arabic,
Hebrew, Myanmar, Khmer, and other scripts use combining characters
(vowels, tone marks, diacritics) that should render on top of or below
their base character — not in a separate cell.

The root cause is in `apply_force_width` within `line_layout.rs`. After
HarfBuzz correctly shapes the text (positioning combining marks at the
same x coordinate as their base character), the force-width loop blindly
increments the cell position counter for **every** glyph — including
zero-advance combining marks. This displaces them into the next cell,
breaking the visual output.

For example, Thai `สวัสดี` renders as `ส ว ั ส ด ี` with vowels and tone
marks scattered across separate cells.

#39526 previously fixed Latin combining characters (like NFD-normalized
é) by handling them at the terminal cell level via
`append_zero_width_chars`. That fix works when alacritty's cell model
already identifies the characters as zero-width. However, many complex
script combining marks still get mispositioned at the shaping layer —
which is what this PR addresses.

## Solution

Extracted `apply_force_width_to_layout()` in `line_layout.rs` that
detects combining marks during the force-width pass by checking whether
a glyph's shaped x position has advanced by at least half a cell width
beyond the previous base character. Combining marks (which HarfBuzz
places at the same x as their base) are kept at their shaped position
relative to the forced base, rather than being pushed to the next cell.

This approach is **script-agnostic** — it works for all complex scripts
(Thai, Arabic, Devanagari, Myanmar, Khmer, etc.) without hardcoding
Unicode ranges or disabling the monospace grid. The fix lives at the
text shaping layer in GPUI, so it benefits any consumer of
`LineLayoutCache` that uses `force_width`.

The two duplicate force-width loops (in `layout_line` and
`layout_line_by_hash`) are consolidated into the single shared helper.

## Testing

**Unit tests** cover the following scenarios:
- Latin text positions remain unchanged
- A single combining mark stays at its base character's cell
- A base character following combining marks gets the correct next cell
- Multiple stacked combining marks all stay anchored to cell 0
- Drifted base positions respect the existing 1px tolerance

**Manual testing** with the following scripts in the integrated
terminal:

| Script | Test text | What to look for |
|--------|-----------|-----------------|
| Thai | `สวัสดีครับ ภาษาไทย ก้อนหิน น้ำตก` | Vowel signs (ั ี) and tone
marks (้) render above/below consonants, not in separate cells |
| Lao | `ສະບາຍດີ ພາສາລາວ` | Vowel signs stay attached to consonants |
| Arabic | `بِسْمِ اللَّهِ الرَّحْمَٰنِ` | Diacritics (harakat) render
on their base letters |
| Hebrew | `בְּרֵאשִׁית בָּרָא אֱלֹהִים` | Vowel points (nikud) stay
under/over their letters |
| Hindi | `नमस्ते हिन्दी भाषा` | Vowel matras render correctly on
consonants |
| Bengali | `বাংলা ভাষা নমস্কার` | Combining marks stay attached |
| Tamil | `தமிழ் மொழி வணக்கம்` | Vowel signs render correctly |
| Myanmar | `မြန်မာဘာသာ မင်္ဂလာပါ` | Combining marks and medial
consonants stay in place |
| Khmer | `ភាសាខ្មែរ សួស្តី` | Subscript consonants and vowel signs
render correctly |

<details>
<summary>Script to reproduce</summary>

```sh
echo "=== Thai ==="
echo "สวัสดีครับ ภาษาไทย ก้อนหิน น้ำตก"
echo ""
echo "=== Lao ==="
echo "ສະບາຍດີ ພາສາລາວ"
echo ""
echo "=== Arabic ==="
echo "بِسْمِ اللَّهِ الرَّحْمَٰنِ"
echo ""
echo "=== Hebrew ==="
echo "בְּרֵאשִׁית בָּרָא אֱלֹהִים"
echo ""
echo "=== Hindi (Devanagari) ==="
echo "नमस्ते हिन्दी भाषा"
echo ""
echo "=== Bengali ==="
echo "বাংলা ভাষা নমস্কার"
echo ""
echo "=== Tamil ==="
echo "தமிழ் மொழி வணக்கம்"
echo ""
echo "=== Myanmar ==="
echo "မြန်မာဘာသာ မင်္ဂလာပါ"
echo ""
echo "=== Khmer ==="
echo "ភាសាខ្មែរ សួស្តី"
```

</details>

### Before

<img width="1144" height="740" alt="Screenshot 2569-04-05 at 10 43 00"
src="https://github.com/user-attachments/assets/bce2075a-9e19-40dd-81ea-0e29a451b781"
/>

### After

<img width="1144" height="747" alt="Screenshot 2569-04-05 at 11 22 55"
src="https://github.com/user-attachments/assets/e0613bf6-8452-4c65-b893-f9931a471b2e"
/>

### Tests passing

<img width="1144" height="740" alt="Screenshot 2569-04-05 at 11 20 22"
src="https://github.com/user-attachments/assets/9100bd77-4ea6-4f77-ae31-5129484552fd"
/>

Release Notes:

- Fixed terminal rendering of combining marks in complex scripts (Thai,
Arabic, Hebrew, Devanagari, Myanmar, Khmer, and others) where vowel
signs and tone marks were incorrectly displaced to adjacent cells
instead of rendering on their base characters.

---------

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
2026-04-23 02:50:58 +00:00
2026-04-06 12:51:13 +00:00
2026-01-22 23:57:26 +05:30
2026-02-03 20:31:02 -07:00
2025-02-04 09:02:59 -05:00
2025-02-04 09:02:59 -05:00
2026-01-06 12:49:51 -08:00
2025-10-27 13:27:59 -04:00
2025-10-17 18:58:14 +00:00
2026-03-17 16:34:35 +00:00

Zed

Zed CI

Welcome to Zed, a high-performance, multiplayer code editor from the creators of Atom and Tree-sitter.


Installation

On macOS, Linux, and Windows you can download Zed directly or install Zed via your local package manager (macOS/Linux/Windows).

Other platforms are not yet available:

Developing Zed

Contributing

See CONTRIBUTING.md for ways you can contribute to Zed.

Also... we're hiring! Check out our jobs page for open roles.

Licensing

License information for third party dependencies must be correctly provided for CI to pass.

We use cargo-about to automatically comply with open source licenses. If CI is failing, check the following:

  • Is it showing a no license specified error for a crate you've created? If so, add publish = false under [package] in your crate's Cargo.toml.
  • Is the error failed to satisfy license requirements for a dependency? If so, first determine what license the project has and whether this system is sufficient to comply with this license's requirements. If you're unsure, ask a lawyer. Once you've verified that this system is acceptable add the license's SPDX identifier to the accepted array in script/licenses/zed-licenses.toml.
  • Is cargo-about unable to find the license for a dependency? If so, add a clarification field at the end of script/licenses/zed-licenses.toml, as specified in the cargo-about book.

Sponsorship

Zed is developed by Zed Industries, Inc., a for-profit company.

If you’d like to financially support the project, you can do so via GitHub Sponsors. Sponsorships go directly to Zed Industries and are used as general company revenue. There are no perks or entitlements associated with sponsorship.

S
Description
No description provided
Readme
411 MiB
0 Stars 1 Watchers 0 Forks
Languages
Rust 94.9%
JSON-with-Comments 2.7%
Python 0.8%
Inno Setup 0.3%
Tree-sitter Query 0.3%
Other 0.5%