Skip to content

editor: Add support for customizable soft wrap indentation - #63468

Open
arusahni wants to merge 17 commits into
zed-industries:mainfrom
arusahni:feat/soft-wrap-indent
Open

arusahni wants to merge 17 commits into
zed-industries:mainfrom
arusahni:feat/soft-wrap-indent

Conversation

@arusahni

@arusahni arusahni commented Aug 31, 2026 •

Copy link
Copy Markdown
Contributor

Objective

  • Soft wrapped lines currently inherit the indentation of their first line. This can be challenging for those coming from different editors, or with different expectations.
  • Allow users to customize the soft wrap behavior to wrap with extra indent, or no indent at all.
  • Fixes Extra indent for soft-wrapped lines #18687

Solution

  • Introduce 3 soft-wrap indent configuration values (in addition to the default of same): none, extra_one, and extra_two (where they are multiples of the indent column width).
  • Plumb indent configuration through to the various gpui and editor components that are concerned with wrapping.
  • Update code action icon placement to accommodate none-level wrap indentation (preceded by a refactor of the function used to calculate its display row).
  • Fixed an existing bug in WrapSnapshot::soft_wrap_indent where querying a 0-indent wrap returned None instead of Some(0)

Testing

  • Added tests to wrap_map.rs for all indentation levels.
  • Updated the random wrap tests to include indent setting combinations.
  • Added tests for code action icon placement.
  • Validated locally by toggling between the various settings and observing how the wrap behaved.

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

Showcase

Changes are grouped by commit to ease review.

Screenshots below!

Click to view showcase

same indent (current Zed behavior)
Screenshot from 2026-08-02 22-45-54

none indent
Screenshot from 2026-08-02 22-45-39

extra_one indent
Screenshot from 2026-08-02 22-46-06

extra_two indent
Screenshot from 2026-08-02 22-46-19

the setting
Screenshot from 2026-08-02 22-42-19


Release Notes:

  • Added a soft_wrap_indent setting ("none", "same", "extra_one", "extra_two") to configure indentation for soft-wrapped continuation lines.
  • Fixed soft-wrap continuation line indentation calculation for unindented lines starting at column 0.
  • [GPUI] Added IndentAdjustment enum to LineWrapper to control indentation of soft-wrapped continuation lines.

@cla-bot cla-bot Bot added the cla-signed The user has signed the Contributor License Agreement label Aug 31, 2026
@arusahni arusahni changed the title Feat/soft wrap indent editor: Add support for customizable soft wrap indentation - #62099 Aug 31, 2026
@arusahni arusahni changed the title editor: Add support for customizable soft wrap indentation - #62099 editor: Add support for customizable soft wrap indentation Aug 31, 2026
@ChristopherBiscardi ChristopherBiscardi added the area:editor Feedback for code editing, formatting, editor iterations, etc label Aug 31, 2026
@SomeoneToIgnore SomeoneToIgnore self-assigned this Aug 31, 2026

@SomeoneToIgnore SomeoneToIgnore 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.

Thank you, needs some more work.

Comment thread crates/settings/src/vscode_import.rs Outdated
Comment thread assets/settings/default.json
Comment thread crates/editor/src/editor.rs
Comment thread crates/editor/src/display_map/wrap_map.rs Outdated
Comment thread crates/editor/src/editor_tests.rs Outdated
Comment thread crates/editor/src/editor_tests.rs Outdated
Comment thread crates/settings_content/src/language.rs
Comment thread crates/editor/src/display_map/wrap_map.rs
@arusahni

arusahni commented Sep 4, 2026

Copy link
Copy Markdown
Contributor Author

@SomeoneToIgnore thanks for the review! I believe I have addressed all your comments. There is an open question I have around the names for the setting values, but thankfully it shouldn't be a huge lift if we decide to go with different nomenclature.

@SomeoneToIgnore SomeoneToIgnore 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.

Getting closer, thank you.

Comment thread crates/editor/src/editor.rs
Comment thread crates/editor/src/config.rs
Comment thread crates/editor/src/editor.rs Outdated
Comment thread crates/editor/src/editor_tests.rs
Comment thread crates/editor/src/editor.rs Outdated
@arusahni

arusahni commented Sep 8, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for the second review! I won't be able to address these comments until next week because I'll be at RustConf.

This allows callers to specify how indentation for soft-wrapped
lines should function. For now this uses the existing default of
same-level indentation.
Also add soft_wrap_indent tests.
This is a 1:1 refactor of the existing calculation. A subsequent commit
will introduce the change that necessitated this refactor.
Because soft-wrapped display rows now can start on a *lower* indent
column than the first display row, we cannot rely on the first row's
indent for code action placement, and instead need to check to see if
there's enough space to display the code action glyph. If not, use the
buffer's first display row for the calculating code action placement.
@arusahni

arusahni commented Sep 20, 2026 •

Copy link
Copy Markdown
Contributor Author

I've addressed all additional feedback as discrete commits and rebased this over main, addressing a merge conflict in editor_tests.rs.

@zed-industries-bot

Copy link
Copy Markdown
Contributor
Fails
🚫

This PR modifies GPUI crates (gpui), which requires a GPUI release notes entry.

Please add at least one entry prefixed with [GPUI] to the "Release Notes" section:

Release Notes:

- Added/Fixed/Improved ...
- [GPUI] Added/Fixed/Improved ...

If the change is not user-facing for GPUI users, use "- [GPUI] N/A" for that entry.

Generated by 🚫 dangerJS against cd6417c

@SomeoneToIgnore SomeoneToIgnore 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.

Seems that we need to deal with line_wrapper code better, also added a few more notes.

Comment on lines +128 to +130
IndentAdjustment::ExtraColumns(extra) => {
Self::MAX_INDENT.min(base_indent + extra)
}

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.

SameIndent is self-bounding: base_indent is measured inside a row that fit, so it never exceeds the wrap column count.
ExtraColumns adds up to 2 × tab_size on top with only the MAX_INDENT clamp, so indent can exceed the wrap width.
After that, width = item_width + indent × space_width > wrap_width holds for every following character, and the line degrades to one character per row, each row wider than the wrap width.

Repro (EditorTestContext, soft_wrap: bounded, preferred_line_length: 10, extra_two, tab size 4, text " ab cd ef gh"):

|      ab |
|              c|
|              d|
|               |
|              e|
|              f|
|               |
|              g|
|              h|

9 rows of 15 columns in a 10-column wrap; same gives 3 rows of ≤ 10 columns.
The overflow is bounded by 2 × tab_size columns, with one row per remaining character, so a 200-character line becomes ~200 rows.
This is reachable with extra_two for any line whose indent is within 8 columns of the pane width, e.g. deeply nested code in a narrow split.

VS Code guards exactly this in computeWrappedTextIndentLength: if (wrappedTextIndentLength + columnsForFullWidthChar > firstLineBreakColumn) wrappedTextIndentLength = 0.
Please add the same guard here (fall back to 0, or to base_indent) and cover it with a unit test in this file.

self.buffer.read(cx).language_settings(cx).soft_wrap_indent
}

pub(super) fn apply_soft_wrap_indent(&mut self, cx: &mut Context<Self>) {

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.

The existing precedent for a settings-driven wrap parameter is soft_wrap: element.rs calls editor.soft_wrap_mode(cx) + set_wrap_width on every prepaint (lines 8579-8589 and 11286-11290), and set_wrap_width early-returns when unchanged.
set_soft_wrap_indent already early-returns the same way.
Reading self.soft_wrap_indent(cx) inside Editor::set_wrap_width below and forwarding it to whichever map is picked there gives the indent the same freshness as soft_wrap/preferred_line_length, and removes the Editor::new, is_minimap, and five event-handler call sites.

It also closes the remaining gap: an empty multibuffer (project search before the first result) whose global soft_wrap_indent changes emits no buffer SettingsChanged, and fetch_applicable_language_settings compares two empty maps, so placeholder_display_map stays stale until an excerpt arrives.

The new tests keep working unmodified: in test mode App::flush_effects draws dirty windows (gpui/src/app.rs:1817-1830), so the poll runs before the next editor.update.
Cost is one extra MultiBuffer::language_settings per frame, which soft_wrap_mode already pays on the same frame.

}

#[gpui::test]
async fn test_soft_wrap_indent_returns_some_for_zero_indent(cx: &mut gpui::TestAppContext) {

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.

The None case in test_soft_wrap_indent above already produces a zero-indent wrap.
One assert_eq!(wrap_snapshot.soft_wrap_indent(WrapRow(1)), Some(0)) there covers this, so this test and its comment block can go.
The same 30 lines of setup are also copied into test_soft_wrap_indent_updates_on_tab_size_change; please share a helper if it stays.

Comment on lines +279 to +280
#[ztracing::instrument(skip_all)]
fn indent_adjustment(&self, tab_size: std::num::NonZeroU32) -> gpui::IndentAdjustment {

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.

Nit: gpui::IndentAdjustment and std::num::NonZeroU32 are spelled out inline throughout this file, please import them.
The ztracing instrument on a one-line match is noise.

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.

Extra indent for soft-wrapped lines

4 participants