diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index ebcc53b09bfbb9466a80d639d17cadfe2927a27e..c4c49eb7911e0d7c5ed375d83697584fbb493b81 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -1097,7 +1097,7 @@ impl DisplaySnapshot { details: &TextLayoutDetails, ) -> u32 { let layout_line = self.layout_row(display_row, details); - layout_line.index_for_x(x) as u32 + layout_line.closest_index_for_x(x) as u32 } pub fn grapheme_at(&self, mut point: DisplayPoint) -> Option { diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 6fd259dae9333933fa7f29041c2deb591b42bf6d..680570d0926257b3bde4532b03681b4515111930 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -8668,7 +8668,7 @@ impl LineWithInvisibles { let fragment_end_x = fragment_start_x + shaped_line.width; if x < fragment_end_x { return Some( - fragment_start_index + shaped_line.index_for_x(x - fragment_start_x), + fragment_start_index + shaped_line.index_for_x(x - fragment_start_x)?, ); } fragment_start_x = fragment_end_x; diff --git a/crates/editor/src/selections_collection.rs b/crates/editor/src/selections_collection.rs index 75fffdc7fea17fe35f9942125499ba15c9a77422..7bb90deda0da84fa8719b9530dffef567c467c36 100644 --- a/crates/editor/src/selections_collection.rs +++ b/crates/editor/src/selections_collection.rs @@ -372,7 +372,7 @@ impl SelectionsCollection { let is_empty = positions.start == positions.end; let line_len = display_map.line_len(row); let line = display_map.layout_row(row, text_layout_details); - let start_col = line.index_for_x(positions.start) as u32; + let start_col = line.closest_index_for_x(positions.start) as u32; let (start, end) = if is_empty { let point = DisplayPoint::new(row, std::cmp::min(start_col, line_len)); @@ -382,7 +382,7 @@ impl SelectionsCollection { return None; } let start = DisplayPoint::new(row, start_col); - let end_col = line.index_for_x(positions.end) as u32; + let end_col = line.closest_index_for_x(positions.end) as u32; let end = DisplayPoint::new(row, end_col); (start, end) }; diff --git a/crates/gpui/examples/input.rs b/crates/gpui/examples/input.rs index 16af30166c6ccdbd06469f4e2fd4cd3df8352127..37115feaa551a787562e7299c9d44bcc97b5fca3 100644 --- a/crates/gpui/examples/input.rs +++ b/crates/gpui/examples/input.rs @@ -178,7 +178,7 @@ impl TextInput { if position.y > bounds.bottom() { return self.content.len(); } - line.index_for_x(position.x - bounds.left()) + line.closest_index_for_x(position.x - bounds.left()) } fn select_to(&mut self, offset: usize, cx: &mut Context) { @@ -380,7 +380,7 @@ impl EntityInputHandler for TextInput { let last_layout = self.last_layout.as_ref()?; assert_eq!(last_layout.text, self.content); - let utf8_index = last_layout.index_for_x(point.x - line_point.x); + let utf8_index = last_layout.index_for_x(point.x - line_point.x)?; Some(self.offset_to_utf16(utf8_index)) } } diff --git a/crates/gpui/src/text_system/line_layout.rs b/crates/gpui/src/text_system/line_layout.rs index 61edd614d804434d414b34a9804e51b0b0148ea4..375a9bdc7bccdddb9d34409c5ced138b2d5aebd2 100644 --- a/crates/gpui/src/text_system/line_layout.rs +++ b/crates/gpui/src/text_system/line_layout.rs @@ -54,9 +54,25 @@ pub struct ShapedGlyph { } impl LineLayout { + /// The index for the character at the given x coordinate + pub fn index_for_x(&self, x: Pixels) -> Option { + if x >= self.width { + None + } else { + for run in self.runs.iter().rev() { + for glyph in run.glyphs.iter().rev() { + if glyph.position.x <= x { + return Some(glyph.index); + } + } + } + Some(0) + } + } + /// closest_index_for_x returns the character boundary closest to the given x coordinate /// (e.g. to handle aligning up/down arrow keys) - pub fn index_for_x(&self, x: Pixels) -> usize { + pub fn closest_index_for_x(&self, x: Pixels) -> usize { let mut prev_index = 0; let mut prev_x = px(0.); @@ -262,10 +278,34 @@ impl WrappedLineLayout { } /// The index corresponding to a given position in this layout for the given line height. + /// + /// See also [`Self::closest_index_for_position`]. pub fn index_for_position( + &self, + position: Point, + line_height: Pixels, + ) -> Result { + self._index_for_position(position, line_height, false) + } + + /// The closest index to a given position in this layout for the given line height. + /// + /// Closest means the character boundary closest to the given position. + /// + /// See also [`LineLayout::closest_index_for_x`]. + pub fn closest_index_for_position( + &self, + position: Point, + line_height: Pixels, + ) -> Result { + self._index_for_position(position, line_height, true) + } + + fn _index_for_position( &self, mut position: Point, line_height: Pixels, + closest: bool, ) -> Result { let wrapped_line_ix = (position.y / line_height) as usize; @@ -305,9 +345,16 @@ impl WrappedLineLayout { } else if position_in_unwrapped_line.x >= wrapped_line_end_x { Err(wrapped_line_end_index) } else { - Ok(self - .unwrapped_layout - .index_for_x(position_in_unwrapped_line.x)) + if closest { + Ok(self + .unwrapped_layout + .closest_index_for_x(position_in_unwrapped_line.x)) + } else { + Ok(self + .unwrapped_layout + .index_for_x(position_in_unwrapped_line.x) + .unwrap()) + } } } diff --git a/crates/vim/src/visual.rs b/crates/vim/src/visual.rs index 498c4b4dc6ec6ad8af4f47bb6ea5044a5fcd3c0a..4172de80afdc1beacbf3ea342846de03953e1fc6 100644 --- a/crates/vim/src/visual.rs +++ b/crates/vim/src/visual.rs @@ -371,10 +371,12 @@ impl Vim { loop { let laid_out_line = map.layout_row(row, &text_layout_details); - let start = - DisplayPoint::new(row, laid_out_line.index_for_x(positions.start) as u32); + let start = DisplayPoint::new( + row, + laid_out_line.closest_index_for_x(positions.start) as u32, + ); let mut end = - DisplayPoint::new(row, laid_out_line.index_for_x(positions.end) as u32); + DisplayPoint::new(row, laid_out_line.closest_index_for_x(positions.end) as u32); if end <= start { if start.column() == map.line_len(start.row()) { end = start;