editor: Fix Python docstring formatting in hover popovers (#59480)

# Objective

Fixes #59426

Python (and possible others like JS/TS ) docstrings in hover popovers
currently have broken indentation and alignment. This happens because
markdown soft breaks (single newlines) are parsed and replaced with
spaces by default. This collapses multi-line Python docstrings into
single wraps, ruining code example layouts and line-wrapped formatting.

## Solution

Configure the `MarkdownStyle` returned by `hover_markdown_style` in
`crates/editor/src/hover_popover.rs` to treat soft breaks as hard breaks
(`soft_break_as_hard_break: true`). This ensures single newlines in
docstrings are preserved and rendered correctly in the hover popover.

## Testing

- Verified hover popovers locally on Python methods/classes to confirm
that multi-line docstring indentation and alignment are preserved.

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

- Fixed Python and Markdown docstring formatting/alignment in hover
popovers.

---------

Co-authored-by: Martin Ye <martin@zed.dev>
This commit is contained in:
Darshan SithanandMartin Ye authored and GitHub committed 2026-06-22 17:35:10 +00:00
1 parent c4103e5ad9
commit 9d696a585c
1 file changed
+41
+41
View File
@@ -739,6 +739,7 @@ pub fn hover_markdown_style(window: &Window, cx: &App) -> MarkdownStyle {
.mt(rems(1.))
.mb_0(),
table_columns_min_size: true,
soft_break_as_hard_break: true,
..Default::default()
}
}
@@ -1275,6 +1276,46 @@ mod tests {
cx.read(|cx: &App| -> u64 { EditorSettings::get_global(cx).hover_popover_delay.0 })
}
#[gpui::test]
fn test_hover_markdown_preserves_soft_breaks(cx: &mut gpui::TestAppContext) {
init_test(cx, |_| {});
let cx = cx.add_empty_window();
let text = concat!(
"class super(object)\n",
"| super(type) -> unbound super object\n",
"| super(type, obj) -> bound super object"
);
let markdown = cx.new(|cx| Markdown::new(text.into(), None, None, cx));
cx.run_until_parked();
let rendered = MarkdownElement::rendered_text(markdown, cx, hover_markdown_style);
// The two soft breaks must render as real newline characters rather
// than being collapsed into spaces.
assert_eq!(
rendered.matches('\n').count(),
2,
"expected two hard line breaks, got {rendered:?}"
);
let lines: Vec<&str> = rendered.split('\n').collect();
assert_eq!(
lines,
[
"class super(object)",
"| super(type) -> unbound super object",
"| super(type, obj) -> bound super object",
]
);
// The two spaces after each `|` continuation marker are preserved verbatim.
assert!(lines[1].starts_with("| super"));
assert!(lines[2].starts_with("| super"));
// No tabs are introduced anywhere in the rendered output.
assert!(!rendered.contains('\t'));
// And the full rendering matches the source exactly.
assert_eq!(rendered, text);
}
impl InfoPopover {
fn get_rendered_text(&self, cx: &gpui::App) -> String {
let mut rendered_text = String::new();