input: Fix track scroll to selected end on drag move. (#1231)

This commit is contained in:
Jason Lee 2025-09-10 13:59:02 +08:00 committed by GitHub
parent 7998d64501
commit 844574befc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 106 additions and 66 deletions

30
Cargo.lock generated
View file

@ -709,7 +709,7 @@ dependencies = [
"bitflags 2.9.1",
"cexpr",
"clang-sys",
"itertools 0.11.0",
"itertools 0.12.1",
"lazy_static",
"lazycell",
"log",
@ -732,7 +732,7 @@ dependencies = [
"bitflags 2.9.1",
"cexpr",
"clang-sys",
"itertools 0.11.0",
"itertools 0.13.0",
"log",
"prettyplease",
"proc-macro2",
@ -1401,7 +1401,7 @@ dependencies = [
[[package]]
name = "collections"
version = "0.1.0"
source = "git+https://github.com/zed-industries/zed.git#5c30578c4961d3766ee991b119658b02741b85e5"
source = "git+https://github.com/zed-industries/zed.git#9c548a0ec6d8b84e2b3b1ee2a78add5fae7a8ba7"
dependencies = [
"indexmap 2.10.0",
"rustc-hash 2.1.1",
@ -1870,7 +1870,7 @@ dependencies = [
[[package]]
name = "derive_refineable"
version = "0.1.0"
source = "git+https://github.com/zed-industries/zed.git#5c30578c4961d3766ee991b119658b02741b85e5"
source = "git+https://github.com/zed-industries/zed.git#9c548a0ec6d8b84e2b3b1ee2a78add5fae7a8ba7"
dependencies = [
"proc-macro2",
"quote",
@ -3049,7 +3049,7 @@ dependencies = [
[[package]]
name = "gpui"
version = "0.1.0"
source = "git+https://github.com/zed-industries/zed.git#5c30578c4961d3766ee991b119658b02741b85e5"
source = "git+https://github.com/zed-industries/zed.git#9c548a0ec6d8b84e2b3b1ee2a78add5fae7a8ba7"
dependencies = [
"anyhow",
"as-raw-xcb-connection",
@ -3232,7 +3232,7 @@ dependencies = [
[[package]]
name = "gpui_macros"
version = "0.1.0"
source = "git+https://github.com/zed-industries/zed.git#5c30578c4961d3766ee991b119658b02741b85e5"
source = "git+https://github.com/zed-industries/zed.git#9c548a0ec6d8b84e2b3b1ee2a78add5fae7a8ba7"
dependencies = [
"heck 0.5.0",
"proc-macro2",
@ -3501,7 +3501,7 @@ dependencies = [
[[package]]
name = "http_client"
version = "0.1.0"
source = "git+https://github.com/zed-industries/zed.git#5c30578c4961d3766ee991b119658b02741b85e5"
source = "git+https://github.com/zed-industries/zed.git#9c548a0ec6d8b84e2b3b1ee2a78add5fae7a8ba7"
dependencies = [
"anyhow",
"bytes",
@ -3521,7 +3521,7 @@ dependencies = [
[[package]]
name = "http_client_tls"
version = "0.1.0"
source = "git+https://github.com/zed-industries/zed.git#5c30578c4961d3766ee991b119658b02741b85e5"
source = "git+https://github.com/zed-industries/zed.git#9c548a0ec6d8b84e2b3b1ee2a78add5fae7a8ba7"
dependencies = [
"rustls",
"rustls-platform-verifier",
@ -4466,7 +4466,7 @@ dependencies = [
[[package]]
name = "media"
version = "0.1.0"
source = "git+https://github.com/zed-industries/zed.git#5c30578c4961d3766ee991b119658b02741b85e5"
source = "git+https://github.com/zed-industries/zed.git#9c548a0ec6d8b84e2b3b1ee2a78add5fae7a8ba7"
dependencies = [
"anyhow",
"bindgen 0.71.1",
@ -6380,7 +6380,7 @@ dependencies = [
[[package]]
name = "refineable"
version = "0.1.0"
source = "git+https://github.com/zed-industries/zed.git#5c30578c4961d3766ee991b119658b02741b85e5"
source = "git+https://github.com/zed-industries/zed.git#9c548a0ec6d8b84e2b3b1ee2a78add5fae7a8ba7"
dependencies = [
"derive_refineable",
"workspace-hack",
@ -6533,7 +6533,7 @@ dependencies = [
[[package]]
name = "reqwest_client"
version = "0.1.0"
source = "git+https://github.com/zed-industries/zed.git#5c30578c4961d3766ee991b119658b02741b85e5"
source = "git+https://github.com/zed-industries/zed.git#9c548a0ec6d8b84e2b3b1ee2a78add5fae7a8ba7"
dependencies = [
"anyhow",
"bytes",
@ -6617,7 +6617,7 @@ dependencies = [
[[package]]
name = "rope"
version = "0.1.0"
source = "git+https://github.com/zed-industries/zed.git#5c30578c4961d3766ee991b119658b02741b85e5"
source = "git+https://github.com/zed-industries/zed.git#9c548a0ec6d8b84e2b3b1ee2a78add5fae7a8ba7"
dependencies = [
"arrayvec",
"log",
@ -7124,7 +7124,7 @@ checksum = "0f7d95a54511e0c7be3f51e8867aa8cf35148d7b9445d44de2f943e2b206e749"
[[package]]
name = "semantic_version"
version = "0.1.0"
source = "git+https://github.com/zed-industries/zed.git#5c30578c4961d3766ee991b119658b02741b85e5"
source = "git+https://github.com/zed-industries/zed.git#9c548a0ec6d8b84e2b3b1ee2a78add5fae7a8ba7"
dependencies = [
"anyhow",
"serde",
@ -7747,7 +7747,7 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
[[package]]
name = "sum_tree"
version = "0.1.0"
source = "git+https://github.com/zed-industries/zed.git#5c30578c4961d3766ee991b119658b02741b85e5"
source = "git+https://github.com/zed-industries/zed.git#9c548a0ec6d8b84e2b3b1ee2a78add5fae7a8ba7"
dependencies = [
"arrayvec",
"log",
@ -9057,7 +9057,7 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "util"
version = "0.1.0"
source = "git+https://github.com/zed-industries/zed.git#5c30578c4961d3766ee991b119658b02741b85e5"
source = "git+https://github.com/zed-industries/zed.git#9c548a0ec6d8b84e2b3b1ee2a78add5fae7a8ba7"
dependencies = [
"anyhow",
"async-fs 2.1.3",

View file

@ -58,7 +58,9 @@ impl TextElement {
///
/// - cursor bounds
/// - scroll offset
/// - current line index
/// - current row index (No only the visible lines, but all lines)
///
/// This method also will update for track scroll to cursor.
fn layout_cursor(
&self,
last_layout: &LastLayout,
@ -66,21 +68,21 @@ impl TextElement {
window: &mut Window,
cx: &mut App,
) -> (Option<Bounds<Pixels>>, Point<Pixels>, Option<usize>) {
let state = self.state.read(cx);
let line_height = last_layout.line_height;
let visible_range = &last_layout.visible_range;
let visible_top = last_layout.visible_top;
let visible_start_offset = last_layout.visible_start_offset;
let lines = &last_layout.lines;
let text_wrapper = &state.text_wrapper;
let line_number_width = last_layout.line_number_width;
let state = self.state.read(cx);
let mut selected_range = state.selected_range;
if let Some(marked_range) = &state.marked_range {
selected_range = (marked_range.end..marked_range.end).into();
}
let cursor = state.cursor();
let mut current_line_index = None;
let mut current_row = None;
let mut scroll_offset = state.scroll_handle.offset();
let mut cursor_bounds = None;
@ -95,49 +97,66 @@ impl TextElement {
let mut cursor_start = None;
let mut cursor_end = None;
let mut prev_lines_offset = visible_start_offset;
let mut offset_y = visible_top;
for (line_ix, line) in lines.iter().enumerate() {
let line_ix = visible_range.start + line_ix;
let mut prev_lines_offset = 0;
let mut offset_y = px(0.);
for (ix, wrap_line) in text_wrapper.lines.iter().enumerate() {
let row = ix;
let line_origin = point(px(0.), offset_y);
// break loop if all cursor positions are found
if cursor_pos.is_some() && cursor_start.is_some() && cursor_end.is_some() {
break;
}
let line_origin = point(px(0.), offset_y);
if cursor_pos.is_none() {
let offset = cursor.offset.saturating_sub(prev_lines_offset);
if let Some(pos) = line.position_for_index(offset, line_height) {
current_line_index = Some(line_ix);
cursor_pos = Some(line_origin + pos);
let in_visible_range = ix >= visible_range.start;
if let Some(line) = in_visible_range
.then(|| lines.get(ix.saturating_sub(visible_range.start)))
.flatten()
{
// If in visible range lines
if cursor_pos.is_none() {
let offset = cursor.offset.saturating_sub(prev_lines_offset);
if let Some(pos) = line.position_for_index(offset, line_height) {
current_row = Some(row);
cursor_pos = Some(line_origin + pos);
}
}
}
if cursor_start.is_none() {
let offset = selected_range.start.saturating_sub(prev_lines_offset);
if let Some(pos) = line.position_for_index(offset, line_height) {
cursor_start = Some(line_origin + pos);
if cursor_start.is_none() {
let offset = selected_range.start.saturating_sub(prev_lines_offset);
if let Some(pos) = line.position_for_index(offset, line_height) {
cursor_start = Some(line_origin + pos);
}
}
}
if cursor_end.is_none() {
let offset = selected_range.end.saturating_sub(prev_lines_offset);
if let Some(pos) = line.position_for_index(offset, line_height) {
cursor_end = Some(line_origin + pos);
if cursor_end.is_none() {
let offset = selected_range.end.saturating_sub(prev_lines_offset);
if let Some(pos) = line.position_for_index(offset, line_height) {
cursor_end = Some(line_origin + pos);
}
}
offset_y += line.size(line_height).height;
// +1 for the last `\n`
prev_lines_offset += line.len() + 1;
} else {
// If not in the visible range.
// Just increase the offset_y and prev_lines_offset.
// This will let the scroll_offset to track the cursor position correctly.
if prev_lines_offset >= cursor.offset && cursor_pos.is_none() {
current_row = Some(row);
cursor_pos = Some(line_origin);
}
if prev_lines_offset >= selected_range.start && cursor_start.is_none() {
cursor_start = Some(line_origin);
}
if prev_lines_offset >= selected_range.end && cursor_end.is_none() {
cursor_end = Some(line_origin);
}
offset_y += wrap_line.height(line_height);
// +1 for the last `\n`
prev_lines_offset += wrap_line.len() + 1;
}
offset_y += line.size(line_height).height;
// +1 for skip the last `\n`
prev_lines_offset += line.len() + 1;
}
// If cursor_pos and cursor_end is still None, it means the cursor is at the end of the text.
if cursor_pos.is_none() {
cursor_pos = Some(point(px(0.), offset_y));
}
if cursor_end.is_none() {
cursor_end = cursor_pos;
}
if let (Some(cursor_pos), Some(cursor_start), Some(cursor_end)) =
@ -206,7 +225,7 @@ impl TextElement {
bounds.origin = bounds.origin + scroll_offset;
(cursor_bounds, scroll_offset, current_line_index)
(cursor_bounds, scroll_offset, current_row)
}
fn layout_selections(
@ -466,8 +485,8 @@ pub(super) struct PrepaintState {
scroll_size: Size<Pixels>,
cursor_bounds: Option<Bounds<Pixels>>,
cursor_scroll_offset: Point<Pixels>,
/// line index (zero based), no wrap, same line as the cursor.
current_line_index: Option<usize>,
/// row index (zero based), no wrap, same line as the cursor.
current_row: Option<usize>,
selection_path: Option<Path<Pixels>>,
bounds: Bounds<Pixels>,
}
@ -755,7 +774,7 @@ impl Element for TextElement {
// Calculate the scroll offset to keep the cursor in view
let (cursor_bounds, cursor_scroll_offset, current_line_index) =
let (cursor_bounds, cursor_scroll_offset, current_row) =
self.layout_cursor(&last_layout, &mut bounds, window, cx);
let selection_path = self.layout_selections(&last_layout, &mut bounds, window, cx);
@ -791,7 +810,7 @@ impl Element for TextElement {
line_no_text.push_str(&"\n ".repeat(line.wrap_boundaries.len()));
}
let runs = if current_line_index == Some(ix) {
let runs = if current_row == Some(ix) {
&current_line_runs
} else {
&other_line_runs
@ -815,7 +834,7 @@ impl Element for TextElement {
line_numbers,
cursor_bounds,
cursor_scroll_offset,
current_line_index,
current_row,
selection_path,
}
}
@ -891,7 +910,8 @@ impl Element for TextElement {
// Each item is the normal lines.
for (ix, lines) in line_numbers.iter().enumerate() {
let is_active = prepaint.current_line_index == Some(visible_range.start + ix);
let row = visible_range.start + ix;
let is_active = prepaint.current_row == Some(row);
for line in lines {
let p = point(input_bounds.origin.x, origin.y + offset_y);
let line_size = line.size(line_height);
@ -958,10 +978,11 @@ impl Element for TextElement {
// Each item is the normal lines.
for (ix, lines) in line_numbers.iter().enumerate() {
let row = visible_range.start + ix;
for line in lines {
let p = point(input_bounds.origin.x, origin.y + offset_y);
let is_active = prepaint.current_line_index == Some(visible_range.start + ix);
let is_active = prepaint.current_row == Some(row);
let line_size = line.size(line_height);
// paint active line number background

View file

@ -769,15 +769,17 @@ impl InputState {
cx: &mut Context<Self>,
) {
let max_point = self.text.max_point();
let line_ix = line.saturating_sub(1).min(max_point.row as usize);
let column_ix = column
let row = line.saturating_sub(1).min(max_point.row as usize);
let col = column
.unwrap_or(1)
.saturating_sub(1)
.min(self.text.line_len(line_ix as u32) as usize);
.min(self.text.line_len(row as u32) as usize);
let offset = self
.text
.point_to_offset(rope::Point::new(line_ix as u32, column_ix as u32));
.point_to_offset(rope::Point::new(row as u32, col as u32));
// TODO: Scroll to make the row in center of viewport.
self.move_to(Cursor::new(offset), window, cx);
self.update_preferred_column();

View file

@ -13,10 +13,27 @@ pub(super) struct LineWrap {
/// like the `window.text_system().shape_text`. So, this value may not equal
/// the actual rendered lines.
pub(super) wrap_lines: usize,
/// The range of the line text in the entire text.
/// The range of the line text in the entire text (not includes ending `\n`).
pub(super) range: Range<usize>,
}
impl LineWrap {
/// Return the bytes length of this line.
pub(super) fn len(&self) -> usize {
self.range.end - self.range.start
}
/// Get the total number of lines including wrapped lines.
pub(super) fn lines_len(&self) -> usize {
self.wrap_lines + 1
}
/// Get the height of this line including wrapped lines.
pub(super) fn height(&self, line_height: Pixels) -> Pixels {
self.lines_len() * line_height
}
}
/// Used to prepare the text with soft wrap to be get lines to displayed in the TextArea
///
/// After use lines to calculate the scroll size of the TextArea