From 43c848893cf0007cda35ba07ca098f8085d95e03 Mon Sep 17 00:00:00 2001 From: Jason Lee Date: Wed, 10 Sep 2025 14:43:11 +0800 Subject: [PATCH] input: Refactor start_of_line, end_of_line by use Rope. (#1232) - Removed `Cursor` struct, now use `usize` for selection offset. --- crates/story/examples/code-editor.rs | 2 +- crates/story/examples/large-text.rs | 2 +- crates/ui/src/input/cursor.rs | 188 ++++++-------------- crates/ui/src/input/element.rs | 10 +- crates/ui/src/input/state.rs | 256 ++++++++++----------------- crates/ui/src/text/inline.rs | 36 ++-- crates/ui/src/text/node.rs | 6 +- 7 files changed, 170 insertions(+), 330 deletions(-) diff --git a/crates/story/examples/code-editor.rs b/crates/story/examples/code-editor.rs index cc0fa154..d44e2de8 100644 --- a/crates/story/examples/code-editor.rs +++ b/crates/story/examples/code-editor.rs @@ -312,7 +312,7 @@ impl Render for Example { Button::new("line-column") .ghost() .xsmall() - .label(format!("{} ({} c)", loc, cursor.offset())) + .label(format!("{} ({} c)", loc, cursor)) .on_click(cx.listener(Self::go_to_line)) }), ), diff --git a/crates/story/examples/large-text.rs b/crates/story/examples/large-text.rs index 28ae23f5..b68265df 100644 --- a/crates/story/examples/large-text.rs +++ b/crates/story/examples/large-text.rs @@ -135,7 +135,7 @@ impl Render for Example { Button::new("line-column") .ghost() .xsmall() - .label(format!("{} ({} c)", loc, cursor.offset())) + .label(format!("{} ({} c)", loc, cursor)) .on_click(cx.listener(Self::go_to_line)) }), ), diff --git a/crates/ui/src/input/cursor.rs b/crates/ui/src/input/cursor.rs index 165c62b2..9664c2d7 100644 --- a/crates/ui/src/input/cursor.rs +++ b/crates/ui/src/input/cursor.rs @@ -1,159 +1,39 @@ -use std::{ - cmp::Ordering, - fmt, - ops::{Add, Deref, Range, Sub}, -}; - -/// Cursor of the text. -#[derive(Debug, Copy, Clone, Default)] -pub struct Cursor { - /// The byte offset in the text (zero-based). - pub(super) offset: usize, -} - -impl Cursor { - pub fn new(offset: usize) -> Self { - Self { offset } - } - - /// Returns the byte offset in the text (zero-based). - pub fn offset(&self) -> usize { - self.offset - } -} - -impl Eq for Cursor {} -impl PartialEq for Cursor { - fn eq(&self, other: &Self) -> bool { - self.offset == other.offset - } -} -impl PartialEq for Cursor { - fn eq(&self, other: &usize) -> bool { - self.offset == *other - } -} -impl PartialEq for usize { - fn eq(&self, other: &Cursor) -> bool { - *self == other.offset - } -} - -impl PartialOrd for Cursor { - fn partial_cmp(&self, other: &Self) -> Option { - self.offset.partial_cmp(&other.offset) - } -} -impl PartialOrd for Cursor { - fn partial_cmp(&self, other: &usize) -> Option { - self.offset.partial_cmp(other) - } -} -impl PartialOrd for usize { - fn partial_cmp(&self, other: &Cursor) -> Option { - self.partial_cmp(&other.offset) - } -} - -impl Add for Cursor { - type Output = Self; - - fn add(mut self, other: Self) -> Self { - self.offset += other.offset; - self - } -} -impl Add for Cursor { - type Output = Self; - - fn add(mut self, other: usize) -> Self { - self.offset += other; - self - } -} -impl Add for usize { - type Output = Cursor; - - fn add(self, other: Cursor) -> Cursor { - Cursor::new(self + other.offset) - } -} - -impl Sub for Cursor { - type Output = Self; - - fn sub(mut self, other: Self) -> Self { - self.offset -= other.offset; - self - } -} -impl Sub for Cursor { - type Output = Self; - - fn sub(mut self, other: usize) -> Self { - self.offset -= other; - self - } -} -impl Sub for usize { - type Output = Cursor; - - fn sub(self, other: Cursor) -> Cursor { - Cursor::new(self - other.offset) - } -} - -impl Deref for Cursor { - type Target = usize; - - fn deref(&self) -> &Self::Target { - &self.offset - } -} +use std::{fmt, ops::Range}; #[derive(Debug, Copy, Clone, PartialEq, Eq, Default)] pub struct Selection { - pub start: Cursor, - pub end: Cursor, + pub start: usize, + pub end: usize, } impl Selection { - pub fn new(start: Cursor, end: Cursor) -> Self { + pub fn new(start: usize, end: usize) -> Self { Self { start, end } } pub fn len(&self) -> usize { - self.end.offset.saturating_sub(self.start.offset) + self.end.saturating_sub(self.start) } pub fn is_empty(&self) -> bool { self.start == self.end } + + /// Clears the selection, setting start and end to 0. + pub fn clear(&mut self) { + self.start = 0; + self.end = 0; + } } -impl From> for Selection { - fn from(value: Range) -> Self { - Self::new(value.start, value.end) - } -} -impl From for Range { - fn from(value: Selection) -> Self { - value.start..value.end - } -} impl From> for Selection { fn from(value: Range) -> Self { - Self::new(Cursor::new(value.start), Cursor::new(value.end)) + Self::new(value.start, value.end) } } impl From for Range { fn from(value: Selection) -> Self { - value.start.offset..value.end.offset - } -} -impl From<&Selection> for Range { - fn from(value: &Selection) -> Self { - value.start.offset..value.end.offset + value.start..value.end } } @@ -166,6 +46,12 @@ pub struct LineColumn { pub column: usize, } +impl LineColumn { + pub fn new(line: usize, column: usize) -> Self { + (line, column).into() + } +} + impl From<(usize, usize)> for LineColumn { fn from(value: (usize, usize)) -> Self { Self { @@ -175,6 +61,15 @@ impl From<(usize, usize)> for LineColumn { } } +impl From for LineColumn { + fn from(value: rope::Point) -> Self { + Self { + line: value.row as usize + 1, + column: value.column as usize + 1, + } + } +} + impl From for tree_sitter::Point { fn from(value: LineColumn) -> Self { Self { @@ -194,6 +89,33 @@ impl fmt::Display for LineColumn { mod tests { use crate::input::LineColumn; + #[test] + fn test_line_column_from_to() { + assert_eq!(LineColumn::new(1, 2), LineColumn { line: 1, column: 2 }); + + assert_eq!(LineColumn::from((1, 2)), LineColumn { line: 1, column: 2 }); + assert_eq!( + LineColumn::from((10, 10)), + LineColumn { + line: 10, + column: 10 + } + ); + assert_eq!(LineColumn::from((0, 0)), LineColumn { line: 1, column: 1 }); + + assert_eq!( + LineColumn::from(rope::Point::new(0, 1)), + LineColumn { line: 1, column: 2 } + ); + assert_eq!( + LineColumn::from(rope::Point::new(10, 9)), + LineColumn { + line: 11, + column: 10 + } + ); + } + #[test] fn test_line_column_display() { assert_eq!(LineColumn::from((1, 2)).to_string(), "1:2"); diff --git a/crates/ui/src/input/element.rs b/crates/ui/src/input/element.rs index 5045903d..7b7c81be 100644 --- a/crates/ui/src/input/element.rs +++ b/crates/ui/src/input/element.rs @@ -115,7 +115,7 @@ impl TextElement { { // If in visible range lines if cursor_pos.is_none() { - let offset = cursor.offset.saturating_sub(prev_lines_offset); + let offset = cursor.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); @@ -142,7 +142,7 @@ impl TextElement { // 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() { + if prev_lines_offset >= cursor && cursor_pos.is_none() { current_row = Some(row); cursor_pos = Some(line_origin); } @@ -679,16 +679,16 @@ impl Element for TextElement { // IME marked text vec![ TextRun { - len: marked_range.start.offset, + len: marked_range.start, ..run.clone() }, TextRun { - len: marked_range.end.offset - marked_range.start.offset, + len: marked_range.end - marked_range.start, underline: marked_run.underline, ..run.clone() }, TextRun { - len: display_text.len() - marked_range.end.offset, + len: display_text.len() - marked_range.end, ..run.clone() }, ] diff --git a/crates/ui/src/input/state.rs b/crates/ui/src/input/state.rs index df121552..efcaf197 100644 --- a/crates/ui/src/input/state.rs +++ b/crates/ui/src/input/state.rs @@ -32,7 +32,7 @@ use super::{ use crate::input::hover_popover::DiagnosticPopover; use crate::input::marker::Marker; use crate::input::text_wrapper::LineWrap; -use crate::input::{Cursor, LineColumn, RopeExt as _, Selection}; +use crate::input::{LineColumn, RopeExt as _, Selection}; use crate::{history::History, scroll::ScrollbarState, Root}; #[derive(Action, Clone, PartialEq, Eq, Deserialize)] @@ -252,7 +252,7 @@ pub struct InputState { /// The marked range is the temporary insert text on IME typing. pub(super) marked_range: Option, pub(super) last_layout: Option, - pub(super) last_cursor: Option, + pub(super) last_cursor: Option, /// The input container bounds pub(super) input_bounds: Bounds, /// The text bounds @@ -323,7 +323,7 @@ impl InputState { ), blink_cursor, history, - selected_range: (Cursor::new(0)..Cursor::new(0)).into(), + selected_range: Selection::default(), selected_word_range: None, selection_reversed: false, marked_range: None, @@ -514,7 +514,7 @@ impl InputState { /// Called after moving the cursor. Updates preferred_column if we know where the cursor now is. fn update_preferred_column(&mut self) { - let column_ix = self.text.offset_to_point(self.cursor().offset).column; + let column_ix = self.text.offset_to_point(self.cursor()).column; self.preferred_column = Some(column_ix as usize); } @@ -558,7 +558,7 @@ impl InputState { return; } - let offset = self.cursor().offset; + let offset = self.cursor(); let was_preferred_column = self.preferred_column; let row = self.text.offset_to_point(offset).row; @@ -570,7 +570,7 @@ impl InputState { let new_offset = line_start_offset + new_column; self.pause_blink_cursor(cx); - self.move_to(Cursor::new(new_offset), window, cx); + self.move_to(new_offset, window, cx); // Set back the preferred_x_offset self.preferred_column = was_preferred_column; cx.notify(); @@ -592,10 +592,9 @@ impl InputState { self.history.ignore = false; // Ensure cursor to start when set text if self.mode.is_single_line() { - self.selected_range = - (Cursor::new(self.text.len())..Cursor::new(self.text.len())).into(); + self.selected_range = (self.text.len()..self.text.len()).into(); } else { - self.selected_range = (Cursor::new(0)..Cursor::new(0)).into(); + self.selected_range.clear(); } // Move scroll to top self.scroll_handle.set_offset(point(px(0.), px(0.))); @@ -613,7 +612,7 @@ impl InputState { cx: &mut Context, ) { let text: SharedString = text.into(); - let range_utf16 = self.range_to_utf16(&(self.cursor().offset..self.cursor().offset)); + let range_utf16 = self.range_to_utf16(&(self.cursor()..self.cursor())); self.replace_text_in_range(Some(range_utf16), &text, window, cx); self.selected_range = (self.selected_range.end..self.selected_range.end).into(); } @@ -742,7 +741,7 @@ impl InputState { /// Return the (1-based) line and column of the cursor. pub fn line_column(&self) -> LineColumn { - let offset = self.cursor().offset; + let offset = self.cursor(); let point = self.text.offset_to_point(offset); LineColumn { @@ -781,7 +780,7 @@ impl InputState { // TODO: Scroll to make the row in center of viewport. - self.move_to(Cursor::new(offset), window, cx); + self.move_to(offset, window, cx); self.update_preferred_column(); self.focus(window, cx); } @@ -794,11 +793,7 @@ impl InputState { pub(super) fn left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context) { self.pause_blink_cursor(cx); if self.selected_range.is_empty() { - self.move_to( - Cursor::new(self.previous_boundary(self.cursor().offset)), - window, - cx, - ); + self.move_to(self.previous_boundary(self.cursor()), window, cx); } else { self.move_to(self.selected_range.start, window, cx) } @@ -807,11 +802,7 @@ impl InputState { pub(super) fn right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context) { self.pause_blink_cursor(cx); if self.selected_range.is_empty() { - self.move_to( - Cursor::new(self.next_boundary(self.selected_range.end.offset)), - window, - cx, - ); + self.move_to(self.next_boundary(self.selected_range.end), window, cx); } else { self.move_to(self.selected_range.end, window, cx) } @@ -824,7 +815,7 @@ impl InputState { if !self.selected_range.is_empty() { self.move_to( - Cursor::new(self.previous_boundary(self.selected_range.start.saturating_sub(1))), + self.previous_boundary(self.selected_range.start.saturating_sub(1)), window, cx, ); @@ -840,7 +831,7 @@ impl InputState { if !self.selected_range.is_empty() { self.move_to( - Cursor::new(self.next_boundary(self.selected_range.end.offset.saturating_sub(1))), + self.next_boundary(self.selected_range.end.saturating_sub(1)), window, cx, ); @@ -887,11 +878,7 @@ impl InputState { window: &mut Window, cx: &mut Context, ) { - self.select_to( - Cursor::new(self.previous_boundary(self.cursor().offset)), - window, - cx, - ); + self.select_to(self.previous_boundary(self.cursor()), window, cx); } pub(super) fn select_right( @@ -900,19 +887,15 @@ impl InputState { window: &mut Window, cx: &mut Context, ) { - self.select_to( - Cursor::new(self.next_boundary(self.cursor().offset)), - window, - cx, - ); + self.select_to(self.next_boundary(self.cursor()), window, cx); } pub(super) fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context) { if self.mode.is_single_line() { return; } - let offset = self.start_of_line(window, cx).saturating_sub(1); - self.select_to(Cursor::new(self.previous_boundary(offset)), window, cx); + let offset = self.start_of_line().saturating_sub(1); + self.select_to(self.previous_boundary(offset), window, cx); } pub(super) fn select_down( @@ -924,8 +907,8 @@ impl InputState { if self.mode.is_single_line() { return; } - let offset = (self.end_of_line(window, cx) + 1).min(self.text.len()); - self.select_to(Cursor::new(self.next_boundary(offset)), window, cx); + let offset = (self.end_of_line() + 1).min(self.text.len()); + self.select_to(self.next_boundary(offset), window, cx); } pub(super) fn select_all( @@ -934,20 +917,20 @@ impl InputState { window: &mut Window, cx: &mut Context, ) { - self.move_to(Cursor::new(0), window, cx); - self.select_to(Cursor::new(self.text.len()), window, cx) + self.move_to(0, window, cx); + self.select_to(self.text.len(), window, cx) } pub(super) fn home(&mut self, _: &MoveHome, window: &mut Window, cx: &mut Context) { self.pause_blink_cursor(cx); - let offset = self.start_of_line(window, cx); - self.move_to(Cursor::new(offset), window, cx); + let offset = self.start_of_line(); + self.move_to(offset, window, cx); } pub(super) fn end(&mut self, _: &MoveEnd, window: &mut Window, cx: &mut Context) { self.pause_blink_cursor(cx); - let offset = self.end_of_line(window, cx); - self.move_to(Cursor::new(offset), window, cx); + let offset = self.end_of_line(); + self.move_to(offset, window, cx); } pub(super) fn move_to_start( @@ -956,7 +939,7 @@ impl InputState { window: &mut Window, cx: &mut Context, ) { - self.move_to(Cursor::new(0), window, cx); + self.move_to(0, window, cx); } pub(super) fn move_to_end( @@ -965,8 +948,7 @@ impl InputState { window: &mut Window, cx: &mut Context, ) { - let end = self.text.len(); - self.move_to(Cursor::new(end), window, cx); + self.move_to(self.text.len(), window, cx); } pub(super) fn move_to_previous_word( @@ -976,7 +958,7 @@ impl InputState { cx: &mut Context, ) { let offset = self.previous_start_of_word(); - self.move_to(Cursor::new(offset), window, cx); + self.move_to(offset, window, cx); } pub(super) fn move_to_next_word( @@ -986,7 +968,7 @@ impl InputState { cx: &mut Context, ) { let offset = self.next_end_of_word(); - self.move_to(Cursor::new(offset), window, cx); + self.move_to(offset, window, cx); } pub(super) fn select_to_start( @@ -995,7 +977,7 @@ impl InputState { window: &mut Window, cx: &mut Context, ) { - self.select_to(Cursor::new(0), window, cx); + self.select_to(0, window, cx); } pub(super) fn select_to_end( @@ -1005,7 +987,7 @@ impl InputState { cx: &mut Context, ) { let end = self.text.len(); - self.select_to(Cursor::new(end), window, cx); + self.select_to(end, window, cx); } pub(super) fn select_to_start_of_line( @@ -1014,8 +996,8 @@ impl InputState { window: &mut Window, cx: &mut Context, ) { - let offset = self.start_of_line(window, cx); - self.select_to(Cursor::new(self.previous_boundary(offset)), window, cx); + let offset = self.start_of_line(); + self.select_to(offset, window, cx); } pub(super) fn select_to_end_of_line( @@ -1024,8 +1006,8 @@ impl InputState { window: &mut Window, cx: &mut Context, ) { - let offset = self.end_of_line(window, cx); - self.select_to(Cursor::new(self.next_boundary(offset)), window, cx); + let offset = self.end_of_line(); + self.select_to(offset, window, cx); } pub(super) fn select_to_previous_word( @@ -1035,7 +1017,7 @@ impl InputState { cx: &mut Context, ) { let offset = self.previous_start_of_word(); - self.select_to(Cursor::new(offset), window, cx); + self.select_to(offset, window, cx); } pub(super) fn select_to_next_word( @@ -1045,12 +1027,12 @@ impl InputState { cx: &mut Context, ) { let offset = self.next_end_of_word(); - self.select_to(Cursor::new(offset), window, cx); + self.select_to(offset, window, cx); } /// Return the start offset of the previous word. fn previous_start_of_word(&mut self) -> usize { - let offset = self.selected_range.start.offset; + let offset = self.selected_range.start; // FIXME: Avoid to_string let left_part = self.text.slice(0..offset).to_string(); @@ -1063,7 +1045,7 @@ impl InputState { /// Return the next end offset of the next word. fn next_end_of_word(&mut self) -> usize { - let offset = self.cursor().offset; + let offset = self.cursor(); let right_part = self.text.slice(offset..self.text.len()).to_string(); UnicodeSegmentation::split_word_bound_indices(right_part.as_str()) @@ -1072,20 +1054,24 @@ impl InputState { .unwrap_or(self.text.len()) } - /// Get start of line - fn start_of_line(&mut self, window: &mut Window, cx: &mut Context) -> usize { + /// Get start of line byte offset of cursor + fn start_of_line(&self) -> usize { if self.mode.is_single_line() { return 0; } - let offset = self.previous_boundary(self.cursor().offset); - let line = self - .text_for_range(self.range_to_utf16(&(0..offset + 1)), &mut None, window, cx) - .unwrap_or_default() - .rfind('\n') - .map(|i| i + 1) - .unwrap_or(0); - line + let row = self.text.offset_to_point(self.cursor()).row; + self.text.line_start_offset(row as usize) + } + + /// Get end of line byte offset of cursor + fn end_of_line(&self) -> usize { + if self.mode.is_single_line() { + return self.text.len(); + } + + let row = self.text.offset_to_point(self.cursor()).row; + self.text.line_end_offset(row as usize) } /// Get start line of selection start or end (The min value). @@ -1096,11 +1082,8 @@ impl InputState { return 0; } - let mut offset = self.previous_boundary( - self.selected_range - .start - .min(self.selected_range.end.offset), - ); + let mut offset = + self.previous_boundary(self.selected_range.start.min(self.selected_range.end)); if self.text.char_at(offset) == Some('\r') { offset += 1; } @@ -1114,60 +1097,18 @@ impl InputState { line } - /// Get end of line - fn end_of_line(&mut self, window: &mut Window, cx: &mut Context) -> usize { - if self.mode.is_single_line() { - return self.text.len(); - } - - // let line = self.text.byte_to_line(self.cursor().offset); - // let offset = self.text.line_to_byte(line) + self.text.line(line).len(); - - let offset = self.next_boundary(self.cursor().offset); - // ignore if offset is "\n" - if self - .text_for_range( - self.range_to_utf16(&(offset.saturating_sub(1)..offset)), - &mut None, - window, - cx, - ) - .unwrap_or_default() - .eq("\n") - { - return offset; - } - - let line = self - .text_for_range( - self.range_to_utf16(&(offset..self.text.len())), - &mut None, - window, - cx, - ) - .unwrap_or_default() - .find('\n') - .map(|i| i + offset) - .unwrap_or(self.text.len()); - line - } - /// Get indent string of next line. /// /// To get current and next line indent, to return more depth one. - pub(super) fn indent_of_next_line( - &mut self, - window: &mut Window, - cx: &mut Context, - ) -> String { + pub(super) fn indent_of_next_line(&mut self) -> String { if self.mode.is_single_line() { return "".into(); } let mut current_indent = String::new(); let mut next_indent = String::new(); - let current_line_start_pos = self.start_of_line(window, cx); - let next_line_start_pos = self.end_of_line(window, cx); + let current_line_start_pos = self.start_of_line(); + let next_line_start_pos = self.end_of_line(); for c in self.text.chars().skip(current_line_start_pos) { if !c.is_whitespace() { break; @@ -1197,11 +1138,7 @@ impl InputState { pub(super) fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context) { if self.selected_range.is_empty() { - self.select_to( - Cursor::new(self.previous_boundary(self.cursor().offset)), - window, - cx, - ) + self.select_to(self.previous_boundary(self.cursor()), window, cx) } self.replace_text_in_range(None, "", window, cx); self.pause_blink_cursor(cx); @@ -1209,11 +1146,7 @@ impl InputState { pub(super) fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context) { if self.selected_range.is_empty() { - self.select_to( - Cursor::new(self.next_boundary(self.cursor().offset)), - window, - cx, - ) + self.select_to(self.next_boundary(self.cursor()), window, cx) } self.replace_text_in_range(None, "", window, cx); self.pause_blink_cursor(cx); @@ -1225,12 +1158,12 @@ impl InputState { window: &mut Window, cx: &mut Context, ) { - let mut offset = self.start_of_line(window, cx); - if offset == self.cursor().offset { + let mut offset = self.start_of_line(); + if offset == self.cursor() { offset = offset.saturating_sub(1); } self.replace_text_in_range( - Some(self.range_to_utf16(&(offset..self.cursor().offset))), + Some(self.range_to_utf16(&(offset..self.cursor()))), "", window, cx, @@ -1245,12 +1178,12 @@ impl InputState { window: &mut Window, cx: &mut Context, ) { - let mut offset = self.end_of_line(window, cx); - if offset == self.cursor().offset { + let mut offset = self.end_of_line(); + if offset == self.cursor() { offset = (offset + 1).clamp(0, self.text.len()); } self.replace_text_in_range( - Some(self.range_to_utf16(&(self.cursor().offset..offset))), + Some(self.range_to_utf16(&(self.cursor()..offset))), "", window, cx, @@ -1266,7 +1199,7 @@ impl InputState { ) { let offset = self.previous_start_of_word(); self.replace_text_in_range( - Some(self.range_to_utf16(&(offset..self.cursor().offset))), + Some(self.range_to_utf16(&(offset..self.cursor()))), "", window, cx, @@ -1282,7 +1215,7 @@ impl InputState { ) { let offset = self.next_end_of_word(); self.replace_text_in_range( - Some(self.range_to_utf16(&(self.cursor().offset..offset))), + Some(self.range_to_utf16(&(self.cursor()..offset))), "", window, cx, @@ -1294,7 +1227,7 @@ impl InputState { if self.mode.is_multi_line() { // Get current line indent let indent = if self.mode.is_code_editor() { - self.indent_of_next_line(window, cx) + self.indent_of_next_line() } else { "".to_string() }; @@ -1359,7 +1292,7 @@ impl InputState { let selected_text = self .text_for_range( - self.range_to_utf16(&(offset..selected_range.end.offset)), + self.range_to_utf16(&(offset..selected_range.end)), &mut None, window, cx, @@ -1379,15 +1312,14 @@ impl InputState { } if is_selected { - self.selected_range = - (Cursor::new(start_offset)..selected_range.end + added_len).into(); + self.selected_range = (start_offset..selected_range.end + added_len).into(); } else { self.selected_range = (selected_range.start + added_len..selected_range.end + added_len).into(); } } else { // Selected none - let offset = self.selected_range.start.offset; + let offset = self.selected_range.start; self.replace_text_in_range( Some(self.range_to_utf16(&(offset..offset))), &tab_indent, @@ -1417,7 +1349,7 @@ impl InputState { let selected_text = self .text_for_range( - self.range_to_utf16(&(offset..selected_range.end.offset)), + self.range_to_utf16(&(offset..selected_range.end)), &mut None, window, cx, @@ -1442,14 +1374,12 @@ impl InputState { } if is_selected { - self.selected_range = (Cursor::new(start_offset) - ..Cursor::new(selected_range.end.saturating_sub(removed_len))) - .into(); - } else { self.selected_range = - (Cursor::new(selected_range.start.saturating_sub(removed_len)) - ..Cursor::new(selected_range.end.saturating_sub(removed_len))) - .into(); + (start_offset..selected_range.end.saturating_sub(removed_len)).into(); + } else { + self.selected_range = (selected_range.start.saturating_sub(removed_len) + ..selected_range.end.saturating_sub(removed_len)) + .into(); } } else { // Selected none @@ -1470,7 +1400,7 @@ impl InputState { ); removed_len = tab_indent.len(); let new_offset = start_offset.saturating_sub(removed_len); - self.selected_range = (Cursor::new(new_offset)..Cursor::new(new_offset)).into(); + self.selected_range = (new_offset..new_offset).into(); } } } @@ -1514,9 +1444,9 @@ impl InputState { } if event.modifiers.shift { - self.select_to(Cursor::new(offset), window, cx); + self.select_to(offset, window, cx); } else { - self.move_to(Cursor::new(offset), window, cx) + self.move_to(offset, window, cx) } } @@ -1671,18 +1601,18 @@ impl InputState { /// The offset is the UTF-8 offset. /// /// Ensure the offset use self.next_boundary or self.previous_boundary to get the correct offset. - fn move_to(&mut self, cursor: Cursor, _: &mut Window, cx: &mut Context) { - let cursor = Cursor::new(cursor.offset.clamp(0, self.text.len())); - self.selected_range = (cursor..cursor).into(); + fn move_to(&mut self, offset: usize, _: &mut Window, cx: &mut Context) { + let offset = offset.clamp(0, self.text.len()); + self.selected_range = (offset..offset).into(); self.pause_blink_cursor(cx); self.update_preferred_column(); cx.notify() } - /// Get the cursor position. + /// Get byte offset of the cursor. /// /// The offset is the UTF-8 offset. - pub fn cursor(&self) -> Cursor { + pub fn cursor(&self) -> usize { if let Some(marked_range) = &self.marked_range { return marked_range.end; } @@ -1814,12 +1744,12 @@ impl InputState { /// The offset is the UTF-8 offset. /// /// Ensure the offset use self.next_boundary or self.previous_boundary to get the correct offset. - fn select_to(&mut self, offset: Cursor, _: &mut Window, cx: &mut Context) { + fn select_to(&mut self, offset: usize, _: &mut Window, cx: &mut Context) { let offset = offset.clamp(0, self.text.len()); if self.selection_reversed { - self.selected_range.start = Cursor::new(offset) + self.selected_range.start = offset } else { - self.selected_range.end = Cursor::new(offset) + self.selected_range.end = offset }; if self.selected_range.end < self.selected_range.start { @@ -1900,7 +1830,7 @@ impl InputState { /// Unselects the currently selected text. pub fn unselect(&mut self, _: &mut Window, cx: &mut Context) { - let offset = self.cursor().offset; + let offset = self.cursor(); self.selected_range = (offset..offset).into(); cx.notify() } @@ -2022,7 +1952,7 @@ impl InputState { } let offset = self.index_for_mouse_position(event.position, window, cx); - self.select_to(Cursor::new(offset), window, cx); + self.select_to(offset, window, cx); } fn is_valid_input(&self, new_text: &str, cx: &mut Context) -> bool { diff --git a/crates/ui/src/text/inline.rs b/crates/ui/src/text/inline.rs index 25832700..5c9ea14f 100644 --- a/crates/ui/src/text/inline.rs +++ b/crates/ui/src/text/inline.rs @@ -7,12 +7,7 @@ use gpui::{ Window, }; -use crate::{ - global_state::GlobalState, - input::{Cursor, Selection}, - text::node::LinkMark, - ActiveTheme, -}; +use crate::{global_state::GlobalState, input::Selection, text::node::LinkMark, ActiveTheme}; /// A inline element used to render a inline text and support selectable. /// @@ -95,7 +90,7 @@ impl Inline { text_layout: &TextLayout, window: &mut Window, cx: &mut App, - ) -> (bool, bool, Option<(usize, usize)>) { + ) -> (bool, bool, Option) { let Some(text_view_state) = GlobalState::global(cx).text_view_state() else { return (false, false, None); }; @@ -112,7 +107,7 @@ impl Inline { // Use for debug selection bounds // self.paint_selected_bounds(selection_bounds, window, cx); - let mut selection = None; + let mut selection: Option = None; let mut offset = 0; let mut chars = self.text.chars().peekable(); while let Some(c) = chars.next() { @@ -130,11 +125,11 @@ impl Inline { if point_in_text_selection(pos, char_width, &selection_bounds, line_height) { if selection.is_none() { - selection = Some((offset, offset)); + selection = Some((offset..offset).into()); } let next_offset = offset + c.len_utf8(); - selection.as_mut().unwrap().1 = next_offset; + selection.as_mut().unwrap().end = next_offset; } offset += c.len_utf8(); @@ -152,15 +147,15 @@ impl Inline { window: &mut Window, cx: &mut App, ) { - let mut start_offset = selection.start.offset(); - let mut end_offset = selection.end.offset(); - if end_offset < start_offset { - std::mem::swap(&mut start_offset, &mut end_offset); + let mut start = selection.start; + let mut end = selection.end; + if end < start { + std::mem::swap(&mut start, &mut end); } - let Some(start_position) = text_layout.position_for_index(start_offset) else { + let Some(start_position) = text_layout.position_for_index(start) else { return; }; - let Some(end_position) = text_layout.position_for_index(end_offset) else { + let Some(end_position) = text_layout.position_for_index(end) else { return; }; @@ -307,14 +302,7 @@ impl Element for Inline { let (is_selectable, is_selection, selection) = self.layout_selections(&text_layout, window, cx); - *state.selection.borrow_mut() = if let Some(selection) = selection { - Some(Selection { - start: Cursor::new(selection.0), - end: Cursor::new(selection.1), - }) - } else { - None - }; + *state.selection.borrow_mut() = selection; if is_selection || is_selectable { window.set_cursor_style(CursorStyle::IBeam, &hitbox); diff --git a/crates/ui/src/text/node.rs b/crates/ui/src/text/node.rs index e1f4a6d5..23c85c9f 100644 --- a/crates/ui/src/text/node.rs +++ b/crates/ui/src/text/node.rs @@ -166,12 +166,12 @@ impl Paragraph { for c in self.children.iter() { if let Some(selection) = c.state.selection.borrow().as_ref() { let part_text = c.state.text.borrow().clone(); - text.push_str(&part_text[selection.start.offset()..selection.end.offset()]); + text.push_str(&part_text[selection.start..selection.end]); } } if let Some(selection) = self.state.selection.borrow().as_ref() { let all_text = self.state.text.borrow().clone(); - text.push_str(&all_text[selection.start.offset()..selection.end.offset()]); + text.push_str(&all_text[selection.start..selection.end]); } text @@ -314,7 +314,7 @@ impl CodeBlock { let mut text = String::new(); if let Some(selection) = self.state.selection.borrow().as_ref() { let part_text = self.state.text.borrow().clone(); - text.push_str(&part_text[selection.start.offset()..selection.end.offset()]); + text.push_str(&part_text[selection.start..selection.end]); } text }