@@ -2044,19 +2044,33 @@ struct RenderedLink {
impl RenderedText {
fn source_index_for_position(&self, position: Point<Pixels>) -> Result<usize, usize> {
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::SettingsStore>() {
+ settings::init(cx);
+ }
+ if !cx.has_global::<theme::GlobalTheme>() {
+ 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,