From 9d696a585cd2c44cb7896d34f5a24f5cdffdcac1 Mon Sep 17 00:00:00 2001 From: Darshan Sithan <40591538+darsh0230@users.noreply.github.com> Date: Mon, 22 Jun 2026 23:05:10 +0530 Subject: [PATCH] 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 --- crates/editor/src/hover_popover.rs | 41 ++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/crates/editor/src/hover_popover.rs b/crates/editor/src/hover_popover.rs index c915232ba5..96aeae1993 100644 --- a/crates/editor/src/hover_popover.rs +++ b/crates/editor/src/hover_popover.rs @@ -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();