input: Fix to position cursor at correct offset on mouse down or select. (#580)

Ref: https://github.com/zed-industries/zed/pull/23603

---

## TODO

Here still have a little bug in multiple lines and soft wrapped line.
Only the first line can get correct position, the wrapped line is always
got the offset +1, so the first char of that soft wrapped line, we can't
position the cursor at the first.
This commit is contained in:
Jason Lee 2025-01-24 22:57:24 +08:00 committed by GitHub
parent 1689f4208f
commit 68de692290
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -12,7 +12,7 @@ use unicode_segmentation::*;
use gpui::prelude::FluentBuilder as _;
use gpui::{
actions, div, point, px, AnyElement, AppContext, Bounds, ClickEvent, ClipboardItem,
Context as _, Entity, EventEmitter, FocusHandle, FocusableView, Half, InteractiveElement as _,
Context as _, Entity, EventEmitter, FocusHandle, FocusableView, InteractiveElement as _,
IntoElement, KeyBinding, KeyDownEvent, Model, MouseButton, MouseDownEvent, MouseMoveEvent,
MouseUpEvent, ParentElement as _, Pixels, Point, Rems, Render, ScrollHandle, ScrollWheelEvent,
SharedString, Styled as _, UTF16Selection, ViewContext, ViewInputHandler, WindowContext,
@ -776,9 +776,9 @@ impl TextInput {
}
if event.modifiers.shift {
self.select_to(self.previous_boundary(offset), cx);
self.select_to(offset, cx);
} else {
self.move_to(self.previous_boundary(offset), cx)
self.move_to(offset, cx)
}
}
@ -896,6 +896,39 @@ impl TextInput {
}
}
// Fork of `closest_index_for_x` method from `gpui::text::Layout`
// https://github.com/zed-industries/zed/blob/d1be419fff415329b38f26aff90488700702c82a/crates/gpui/src/text_system/line_layout.rs#L74
fn closest_index_for_x(&self, line: &WrappedLine, x: Pixels) -> usize {
let mut prev_index = 0;
let mut prev_x = px(0.);
for run in line.unwrapped_layout.runs.iter() {
for glyph in run.glyphs.iter() {
if glyph.position.x >= x {
if glyph.position.x - x < x - prev_x {
return glyph.index;
} else {
return prev_index;
}
}
prev_index = glyph.index;
prev_x = glyph.position.x;
}
}
// HOTFIX:
//
// Wait https://github.com/zed-industries/zed/pull/23603
if line.unwrapped_layout.len == 1 {
if x > line.width() / 2.0 {
return 1;
}
return 0;
}
line.unwrapped_layout.len
}
fn index_for_mouse_position(&self, position: Point<Pixels>, _: &WindowContext) -> usize {
// If the text is empty, always return 0
if self.text.is_empty() {
@ -926,16 +959,25 @@ impl TextInput {
for line in lines.iter() {
let line_origin = self.line_origin_with_y_offset(&mut y_offset, &line, line_height);
let mut pos = inner_position - line_origin;
// Ignore the y position in single line mode, only check x position.
let pos = inner_position - line_origin;
let closest_index = self.closest_index_for_x(line, pos.x);
// Return offset by use closest_index_for_x if is single line mode.
if self.is_single_line() {
pos.y = line_height.half();
return closest_index;
}
let index_result = line.index_for_position(pos, line_height);
if let Ok(v) = index_result {
// Add 1 for place cursor after the character.
index += v + 1;
// FIXME: If the line have soft wrap, only first line can get correct position (closest_index is correct).
// The second line or more, the position is always at offset +1,
// but if we mouse down on left half or character, we expect the cursor is at offset.
// We need find a way to improve.
if pos.y > line_height / 2. {
index += v + 1;
} else {
index += closest_index;
}
break;
} else if let Ok(_) = line.index_for_position(point(px(0.), pos.y), line_height) {
// Click in the this line but not in the text, move cursor to the end of the line.
@ -1168,7 +1210,7 @@ impl TextInput {
}
let offset = self.index_for_mouse_position(event.position, cx);
self.select_to(self.previous_boundary(offset), cx);
self.select_to(offset, cx);
}
fn is_valid_input(&self, new_text: &str) -> bool {