diff --git a/crates/markdown/src/markdown.rs b/crates/markdown/src/markdown.rs index f5e3ee1366cbae0a91c24efde1f034d2a2d5ffb1..c87491435200b6826708174d8f9946ec6942ca3f 100644 --- a/crates/markdown/src/markdown.rs +++ b/crates/markdown/src/markdown.rs @@ -2044,19 +2044,33 @@ struct RenderedLink { impl RenderedText { fn source_index_for_position(&self, position: Point) -> Result { let mut lines = self.lines.iter().peekable(); + let mut fallback_line: Option<&RenderedLine> = None; while let Some(line) = lines.next() { let line_bounds = line.layout.bounds(); + + // Exact match: position is within bounds (handles overlapping bounds like table columns) + if line_bounds.contains(&position) { + return line.source_index_for_position(position); + } + + // Track fallback for Y-coordinate based matching + if position.y <= line_bounds.bottom() && fallback_line.is_none() { + fallback_line = Some(line); + } + + // Handle gap between lines if position.y > line_bounds.bottom() { if let Some(next_line) = lines.peek() && position.y < next_line.layout.bounds().top() { return Err(line.source_end); } - - continue; } + } + // Fall back to Y-coordinate matched line + if let Some(line) = fallback_line { return line.source_index_for_position(position); } @@ -2188,6 +2202,17 @@ mod tests { use language::{Language, LanguageConfig, LanguageMatcher}; use std::sync::Arc; + fn ensure_theme_initialized(cx: &mut TestAppContext) { + cx.update(|cx| { + if !cx.has_global::() { + settings::init(cx); + } + if !cx.has_global::() { + theme::init(theme::LoadThemes::JustBase, cx); + } + }); + } + #[gpui::test] fn test_mappings(cx: &mut TestAppContext) { // Formatting. @@ -2256,6 +2281,8 @@ mod tests { } } + ensure_theme_initialized(cx); + let (_, cx) = cx.add_window_view(|_, _| TestWindow); let markdown = cx.new(|cx| Markdown::new(markdown.to_string().into(), language_registry, None, cx)); @@ -2413,6 +2440,28 @@ mod tests { assert_eq!(selected_text, "code"); } + #[gpui::test] + fn test_table_column_selection(cx: &mut TestAppContext) { + let rendered = render_markdown("| a | b |\n|---|---|\n| c | d |", cx); + + assert!(rendered.lines.len() >= 2); + let first_bounds = rendered.lines[0].layout.bounds(); + let second_bounds = rendered.lines[1].layout.bounds(); + + let first_index = match rendered.source_index_for_position(first_bounds.center()) { + Ok(index) | Err(index) => index, + }; + let second_index = match rendered.source_index_for_position(second_bounds.center()) { + Ok(index) | Err(index) => index, + }; + + let first_word = rendered.text_for_range(rendered.surrounding_word_range(first_index)); + let second_word = rendered.text_for_range(rendered.surrounding_word_range(second_index)); + + assert_eq!(first_word, "a"); + assert_eq!(second_word, "b"); + } + #[gpui::test] fn test_inline_code_word_selection_excludes_backticks(cx: &mut TestAppContext) { // Test that double-clicking on inline code selects just the code content,