input: Refactor start_of_line, end_of_line by use Rope. (#1232)

- Removed `Cursor` struct, now use `usize` for selection offset.
This commit is contained in:
Jason Lee 2025-09-10 14:43:11 +08:00 committed by GitHub
parent 844574befc
commit 43c848893c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 170 additions and 330 deletions

View file

@ -312,7 +312,7 @@ impl Render for Example {
Button::new("line-column") Button::new("line-column")
.ghost() .ghost()
.xsmall() .xsmall()
.label(format!("{} ({} c)", loc, cursor.offset())) .label(format!("{} ({} c)", loc, cursor))
.on_click(cx.listener(Self::go_to_line)) .on_click(cx.listener(Self::go_to_line))
}), }),
), ),

View file

@ -135,7 +135,7 @@ impl Render for Example {
Button::new("line-column") Button::new("line-column")
.ghost() .ghost()
.xsmall() .xsmall()
.label(format!("{} ({} c)", loc, cursor.offset())) .label(format!("{} ({} c)", loc, cursor))
.on_click(cx.listener(Self::go_to_line)) .on_click(cx.listener(Self::go_to_line))
}), }),
), ),

View file

@ -1,159 +1,39 @@
use std::{ use std::{fmt, ops::Range};
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<usize> for Cursor {
fn eq(&self, other: &usize) -> bool {
self.offset == *other
}
}
impl PartialEq<Cursor> for usize {
fn eq(&self, other: &Cursor) -> bool {
*self == other.offset
}
}
impl PartialOrd for Cursor {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
self.offset.partial_cmp(&other.offset)
}
}
impl PartialOrd<usize> for Cursor {
fn partial_cmp(&self, other: &usize) -> Option<Ordering> {
self.offset.partial_cmp(other)
}
}
impl PartialOrd<Cursor> for usize {
fn partial_cmp(&self, other: &Cursor) -> Option<Ordering> {
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<usize> for Cursor {
type Output = Self;
fn add(mut self, other: usize) -> Self {
self.offset += other;
self
}
}
impl Add<Cursor> 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<usize> for Cursor {
type Output = Self;
fn sub(mut self, other: usize) -> Self {
self.offset -= other;
self
}
}
impl Sub<Cursor> 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
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)] #[derive(Debug, Copy, Clone, PartialEq, Eq, Default)]
pub struct Selection { pub struct Selection {
pub start: Cursor, pub start: usize,
pub end: Cursor, pub end: usize,
} }
impl Selection { impl Selection {
pub fn new(start: Cursor, end: Cursor) -> Self { pub fn new(start: usize, end: usize) -> Self {
Self { start, end } Self { start, end }
} }
pub fn len(&self) -> usize { 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 { pub fn is_empty(&self) -> bool {
self.start == self.end 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<Range<Cursor>> for Selection {
fn from(value: Range<Cursor>) -> Self {
Self::new(value.start, value.end)
}
}
impl From<Selection> for Range<Cursor> {
fn from(value: Selection) -> Self {
value.start..value.end
}
}
impl From<Range<usize>> for Selection { impl From<Range<usize>> for Selection {
fn from(value: Range<usize>) -> Self { fn from(value: Range<usize>) -> Self {
Self::new(Cursor::new(value.start), Cursor::new(value.end)) Self::new(value.start, value.end)
} }
} }
impl From<Selection> for Range<usize> { impl From<Selection> for Range<usize> {
fn from(value: Selection) -> Self { fn from(value: Selection) -> Self {
value.start.offset..value.end.offset value.start..value.end
}
}
impl From<&Selection> for Range<usize> {
fn from(value: &Selection) -> Self {
value.start.offset..value.end.offset
} }
} }
@ -166,6 +46,12 @@ pub struct LineColumn {
pub column: usize, pub column: usize,
} }
impl LineColumn {
pub fn new(line: usize, column: usize) -> Self {
(line, column).into()
}
}
impl From<(usize, usize)> for LineColumn { impl From<(usize, usize)> for LineColumn {
fn from(value: (usize, usize)) -> Self { fn from(value: (usize, usize)) -> Self {
Self { Self {
@ -175,6 +61,15 @@ impl From<(usize, usize)> for LineColumn {
} }
} }
impl From<rope::Point> for LineColumn {
fn from(value: rope::Point) -> Self {
Self {
line: value.row as usize + 1,
column: value.column as usize + 1,
}
}
}
impl From<LineColumn> for tree_sitter::Point { impl From<LineColumn> for tree_sitter::Point {
fn from(value: LineColumn) -> Self { fn from(value: LineColumn) -> Self {
Self { Self {
@ -194,6 +89,33 @@ impl fmt::Display for LineColumn {
mod tests { mod tests {
use crate::input::LineColumn; 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] #[test]
fn test_line_column_display() { fn test_line_column_display() {
assert_eq!(LineColumn::from((1, 2)).to_string(), "1:2"); assert_eq!(LineColumn::from((1, 2)).to_string(), "1:2");

View file

@ -115,7 +115,7 @@ impl TextElement {
{ {
// If in visible range lines // If in visible range lines
if cursor_pos.is_none() { 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) { if let Some(pos) = line.position_for_index(offset, line_height) {
current_row = Some(row); current_row = Some(row);
cursor_pos = Some(line_origin + pos); cursor_pos = Some(line_origin + pos);
@ -142,7 +142,7 @@ impl TextElement {
// Just increase the offset_y and prev_lines_offset. // Just increase the offset_y and prev_lines_offset.
// This will let the scroll_offset to track the cursor position correctly. // 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); current_row = Some(row);
cursor_pos = Some(line_origin); cursor_pos = Some(line_origin);
} }
@ -679,16 +679,16 @@ impl Element for TextElement {
// IME marked text // IME marked text
vec![ vec![
TextRun { TextRun {
len: marked_range.start.offset, len: marked_range.start,
..run.clone() ..run.clone()
}, },
TextRun { TextRun {
len: marked_range.end.offset - marked_range.start.offset, len: marked_range.end - marked_range.start,
underline: marked_run.underline, underline: marked_run.underline,
..run.clone() ..run.clone()
}, },
TextRun { TextRun {
len: display_text.len() - marked_range.end.offset, len: display_text.len() - marked_range.end,
..run.clone() ..run.clone()
}, },
] ]

View file

@ -32,7 +32,7 @@ use super::{
use crate::input::hover_popover::DiagnosticPopover; use crate::input::hover_popover::DiagnosticPopover;
use crate::input::marker::Marker; use crate::input::marker::Marker;
use crate::input::text_wrapper::LineWrap; 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}; use crate::{history::History, scroll::ScrollbarState, Root};
#[derive(Action, Clone, PartialEq, Eq, Deserialize)] #[derive(Action, Clone, PartialEq, Eq, Deserialize)]
@ -252,7 +252,7 @@ pub struct InputState {
/// The marked range is the temporary insert text on IME typing. /// The marked range is the temporary insert text on IME typing.
pub(super) marked_range: Option<Selection>, pub(super) marked_range: Option<Selection>,
pub(super) last_layout: Option<LastLayout>, pub(super) last_layout: Option<LastLayout>,
pub(super) last_cursor: Option<Cursor>, pub(super) last_cursor: Option<usize>,
/// The input container bounds /// The input container bounds
pub(super) input_bounds: Bounds<Pixels>, pub(super) input_bounds: Bounds<Pixels>,
/// The text bounds /// The text bounds
@ -323,7 +323,7 @@ impl InputState {
), ),
blink_cursor, blink_cursor,
history, history,
selected_range: (Cursor::new(0)..Cursor::new(0)).into(), selected_range: Selection::default(),
selected_word_range: None, selected_word_range: None,
selection_reversed: false, selection_reversed: false,
marked_range: None, 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. /// Called after moving the cursor. Updates preferred_column if we know where the cursor now is.
fn update_preferred_column(&mut self) { 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); self.preferred_column = Some(column_ix as usize);
} }
@ -558,7 +558,7 @@ impl InputState {
return; return;
} }
let offset = self.cursor().offset; let offset = self.cursor();
let was_preferred_column = self.preferred_column; let was_preferred_column = self.preferred_column;
let row = self.text.offset_to_point(offset).row; let row = self.text.offset_to_point(offset).row;
@ -570,7 +570,7 @@ impl InputState {
let new_offset = line_start_offset + new_column; let new_offset = line_start_offset + new_column;
self.pause_blink_cursor(cx); 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 // Set back the preferred_x_offset
self.preferred_column = was_preferred_column; self.preferred_column = was_preferred_column;
cx.notify(); cx.notify();
@ -592,10 +592,9 @@ impl InputState {
self.history.ignore = false; self.history.ignore = false;
// Ensure cursor to start when set text // Ensure cursor to start when set text
if self.mode.is_single_line() { if self.mode.is_single_line() {
self.selected_range = self.selected_range = (self.text.len()..self.text.len()).into();
(Cursor::new(self.text.len())..Cursor::new(self.text.len())).into();
} else { } else {
self.selected_range = (Cursor::new(0)..Cursor::new(0)).into(); self.selected_range.clear();
} }
// Move scroll to top // Move scroll to top
self.scroll_handle.set_offset(point(px(0.), px(0.))); self.scroll_handle.set_offset(point(px(0.), px(0.)));
@ -613,7 +612,7 @@ impl InputState {
cx: &mut Context<Self>, cx: &mut Context<Self>,
) { ) {
let text: SharedString = text.into(); 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.replace_text_in_range(Some(range_utf16), &text, window, cx);
self.selected_range = (self.selected_range.end..self.selected_range.end).into(); 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. /// Return the (1-based) line and column of the cursor.
pub fn line_column(&self) -> LineColumn { pub fn line_column(&self) -> LineColumn {
let offset = self.cursor().offset; let offset = self.cursor();
let point = self.text.offset_to_point(offset); let point = self.text.offset_to_point(offset);
LineColumn { LineColumn {
@ -781,7 +780,7 @@ impl InputState {
// TODO: Scroll to make the row in center of viewport. // 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.update_preferred_column();
self.focus(window, cx); self.focus(window, cx);
} }
@ -794,11 +793,7 @@ impl InputState {
pub(super) fn left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) { pub(super) fn left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
self.pause_blink_cursor(cx); self.pause_blink_cursor(cx);
if self.selected_range.is_empty() { if self.selected_range.is_empty() {
self.move_to( self.move_to(self.previous_boundary(self.cursor()), window, cx);
Cursor::new(self.previous_boundary(self.cursor().offset)),
window,
cx,
);
} else { } else {
self.move_to(self.selected_range.start, window, cx) 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>) { pub(super) fn right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
self.pause_blink_cursor(cx); self.pause_blink_cursor(cx);
if self.selected_range.is_empty() { if self.selected_range.is_empty() {
self.move_to( self.move_to(self.next_boundary(self.selected_range.end), window, cx);
Cursor::new(self.next_boundary(self.selected_range.end.offset)),
window,
cx,
);
} else { } else {
self.move_to(self.selected_range.end, window, cx) self.move_to(self.selected_range.end, window, cx)
} }
@ -824,7 +815,7 @@ impl InputState {
if !self.selected_range.is_empty() { if !self.selected_range.is_empty() {
self.move_to( 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, window,
cx, cx,
); );
@ -840,7 +831,7 @@ impl InputState {
if !self.selected_range.is_empty() { if !self.selected_range.is_empty() {
self.move_to( 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, window,
cx, cx,
); );
@ -887,11 +878,7 @@ impl InputState {
window: &mut Window, window: &mut Window,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) { ) {
self.select_to( self.select_to(self.previous_boundary(self.cursor()), window, cx);
Cursor::new(self.previous_boundary(self.cursor().offset)),
window,
cx,
);
} }
pub(super) fn select_right( pub(super) fn select_right(
@ -900,19 +887,15 @@ impl InputState {
window: &mut Window, window: &mut Window,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) { ) {
self.select_to( self.select_to(self.next_boundary(self.cursor()), window, cx);
Cursor::new(self.next_boundary(self.cursor().offset)),
window,
cx,
);
} }
pub(super) fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) { pub(super) fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
if self.mode.is_single_line() { if self.mode.is_single_line() {
return; return;
} }
let offset = self.start_of_line(window, cx).saturating_sub(1); let offset = self.start_of_line().saturating_sub(1);
self.select_to(Cursor::new(self.previous_boundary(offset)), window, cx); self.select_to(self.previous_boundary(offset), window, cx);
} }
pub(super) fn select_down( pub(super) fn select_down(
@ -924,8 +907,8 @@ impl InputState {
if self.mode.is_single_line() { if self.mode.is_single_line() {
return; return;
} }
let offset = (self.end_of_line(window, cx) + 1).min(self.text.len()); let offset = (self.end_of_line() + 1).min(self.text.len());
self.select_to(Cursor::new(self.next_boundary(offset)), window, cx); self.select_to(self.next_boundary(offset), window, cx);
} }
pub(super) fn select_all( pub(super) fn select_all(
@ -934,20 +917,20 @@ impl InputState {
window: &mut Window, window: &mut Window,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) { ) {
self.move_to(Cursor::new(0), window, cx); self.move_to(0, window, cx);
self.select_to(Cursor::new(self.text.len()), window, cx) self.select_to(self.text.len(), window, cx)
} }
pub(super) fn home(&mut self, _: &MoveHome, window: &mut Window, cx: &mut Context<Self>) { pub(super) fn home(&mut self, _: &MoveHome, window: &mut Window, cx: &mut Context<Self>) {
self.pause_blink_cursor(cx); self.pause_blink_cursor(cx);
let offset = self.start_of_line(window, cx); let offset = self.start_of_line();
self.move_to(Cursor::new(offset), window, cx); self.move_to(offset, window, cx);
} }
pub(super) fn end(&mut self, _: &MoveEnd, window: &mut Window, cx: &mut Context<Self>) { pub(super) fn end(&mut self, _: &MoveEnd, window: &mut Window, cx: &mut Context<Self>) {
self.pause_blink_cursor(cx); self.pause_blink_cursor(cx);
let offset = self.end_of_line(window, cx); let offset = self.end_of_line();
self.move_to(Cursor::new(offset), window, cx); self.move_to(offset, window, cx);
} }
pub(super) fn move_to_start( pub(super) fn move_to_start(
@ -956,7 +939,7 @@ impl InputState {
window: &mut Window, window: &mut Window,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) { ) {
self.move_to(Cursor::new(0), window, cx); self.move_to(0, window, cx);
} }
pub(super) fn move_to_end( pub(super) fn move_to_end(
@ -965,8 +948,7 @@ impl InputState {
window: &mut Window, window: &mut Window,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) { ) {
let end = self.text.len(); self.move_to(self.text.len(), window, cx);
self.move_to(Cursor::new(end), window, cx);
} }
pub(super) fn move_to_previous_word( pub(super) fn move_to_previous_word(
@ -976,7 +958,7 @@ impl InputState {
cx: &mut Context<Self>, cx: &mut Context<Self>,
) { ) {
let offset = self.previous_start_of_word(); 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( pub(super) fn move_to_next_word(
@ -986,7 +968,7 @@ impl InputState {
cx: &mut Context<Self>, cx: &mut Context<Self>,
) { ) {
let offset = self.next_end_of_word(); 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( pub(super) fn select_to_start(
@ -995,7 +977,7 @@ impl InputState {
window: &mut Window, window: &mut Window,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) { ) {
self.select_to(Cursor::new(0), window, cx); self.select_to(0, window, cx);
} }
pub(super) fn select_to_end( pub(super) fn select_to_end(
@ -1005,7 +987,7 @@ impl InputState {
cx: &mut Context<Self>, cx: &mut Context<Self>,
) { ) {
let end = self.text.len(); 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( pub(super) fn select_to_start_of_line(
@ -1014,8 +996,8 @@ impl InputState {
window: &mut Window, window: &mut Window,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) { ) {
let offset = self.start_of_line(window, cx); let offset = self.start_of_line();
self.select_to(Cursor::new(self.previous_boundary(offset)), window, cx); self.select_to(offset, window, cx);
} }
pub(super) fn select_to_end_of_line( pub(super) fn select_to_end_of_line(
@ -1024,8 +1006,8 @@ impl InputState {
window: &mut Window, window: &mut Window,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) { ) {
let offset = self.end_of_line(window, cx); let offset = self.end_of_line();
self.select_to(Cursor::new(self.next_boundary(offset)), window, cx); self.select_to(offset, window, cx);
} }
pub(super) fn select_to_previous_word( pub(super) fn select_to_previous_word(
@ -1035,7 +1017,7 @@ impl InputState {
cx: &mut Context<Self>, cx: &mut Context<Self>,
) { ) {
let offset = self.previous_start_of_word(); 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( pub(super) fn select_to_next_word(
@ -1045,12 +1027,12 @@ impl InputState {
cx: &mut Context<Self>, cx: &mut Context<Self>,
) { ) {
let offset = self.next_end_of_word(); 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. /// Return the start offset of the previous word.
fn previous_start_of_word(&mut self) -> usize { 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 // FIXME: Avoid to_string
let left_part = self.text.slice(0..offset).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. /// Return the next end offset of the next word.
fn next_end_of_word(&mut self) -> usize { 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(); let right_part = self.text.slice(offset..self.text.len()).to_string();
UnicodeSegmentation::split_word_bound_indices(right_part.as_str()) UnicodeSegmentation::split_word_bound_indices(right_part.as_str())
@ -1072,20 +1054,24 @@ impl InputState {
.unwrap_or(self.text.len()) .unwrap_or(self.text.len())
} }
/// Get start of line /// Get start of line byte offset of cursor
fn start_of_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> usize { fn start_of_line(&self) -> usize {
if self.mode.is_single_line() { if self.mode.is_single_line() {
return 0; return 0;
} }
let offset = self.previous_boundary(self.cursor().offset); let row = self.text.offset_to_point(self.cursor()).row;
let line = self self.text.line_start_offset(row as usize)
.text_for_range(self.range_to_utf16(&(0..offset + 1)), &mut None, window, cx) }
.unwrap_or_default()
.rfind('\n') /// Get end of line byte offset of cursor
.map(|i| i + 1) fn end_of_line(&self) -> usize {
.unwrap_or(0); if self.mode.is_single_line() {
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). /// Get start line of selection start or end (The min value).
@ -1096,11 +1082,8 @@ impl InputState {
return 0; return 0;
} }
let mut offset = self.previous_boundary( let mut offset =
self.selected_range self.previous_boundary(self.selected_range.start.min(self.selected_range.end));
.start
.min(self.selected_range.end.offset),
);
if self.text.char_at(offset) == Some('\r') { if self.text.char_at(offset) == Some('\r') {
offset += 1; offset += 1;
} }
@ -1114,60 +1097,18 @@ impl InputState {
line line
} }
/// Get end of line
fn end_of_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> 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. /// Get indent string of next line.
/// ///
/// To get current and next line indent, to return more depth one. /// To get current and next line indent, to return more depth one.
pub(super) fn indent_of_next_line( pub(super) fn indent_of_next_line(&mut self) -> String {
&mut self,
window: &mut Window,
cx: &mut Context<Self>,
) -> String {
if self.mode.is_single_line() { if self.mode.is_single_line() {
return "".into(); return "".into();
} }
let mut current_indent = String::new(); let mut current_indent = String::new();
let mut next_indent = String::new(); let mut next_indent = String::new();
let current_line_start_pos = self.start_of_line(window, cx); let current_line_start_pos = self.start_of_line();
let next_line_start_pos = self.end_of_line(window, cx); let next_line_start_pos = self.end_of_line();
for c in self.text.chars().skip(current_line_start_pos) { for c in self.text.chars().skip(current_line_start_pos) {
if !c.is_whitespace() { if !c.is_whitespace() {
break; break;
@ -1197,11 +1138,7 @@ impl InputState {
pub(super) fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) { pub(super) fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
if self.selected_range.is_empty() { if self.selected_range.is_empty() {
self.select_to( self.select_to(self.previous_boundary(self.cursor()), window, cx)
Cursor::new(self.previous_boundary(self.cursor().offset)),
window,
cx,
)
} }
self.replace_text_in_range(None, "", window, cx); self.replace_text_in_range(None, "", window, cx);
self.pause_blink_cursor(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<Self>) { pub(super) fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
if self.selected_range.is_empty() { if self.selected_range.is_empty() {
self.select_to( self.select_to(self.next_boundary(self.cursor()), window, cx)
Cursor::new(self.next_boundary(self.cursor().offset)),
window,
cx,
)
} }
self.replace_text_in_range(None, "", window, cx); self.replace_text_in_range(None, "", window, cx);
self.pause_blink_cursor(cx); self.pause_blink_cursor(cx);
@ -1225,12 +1158,12 @@ impl InputState {
window: &mut Window, window: &mut Window,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) { ) {
let mut offset = self.start_of_line(window, cx); let mut offset = self.start_of_line();
if offset == self.cursor().offset { if offset == self.cursor() {
offset = offset.saturating_sub(1); offset = offset.saturating_sub(1);
} }
self.replace_text_in_range( self.replace_text_in_range(
Some(self.range_to_utf16(&(offset..self.cursor().offset))), Some(self.range_to_utf16(&(offset..self.cursor()))),
"", "",
window, window,
cx, cx,
@ -1245,12 +1178,12 @@ impl InputState {
window: &mut Window, window: &mut Window,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) { ) {
let mut offset = self.end_of_line(window, cx); let mut offset = self.end_of_line();
if offset == self.cursor().offset { if offset == self.cursor() {
offset = (offset + 1).clamp(0, self.text.len()); offset = (offset + 1).clamp(0, self.text.len());
} }
self.replace_text_in_range( self.replace_text_in_range(
Some(self.range_to_utf16(&(self.cursor().offset..offset))), Some(self.range_to_utf16(&(self.cursor()..offset))),
"", "",
window, window,
cx, cx,
@ -1266,7 +1199,7 @@ impl InputState {
) { ) {
let offset = self.previous_start_of_word(); let offset = self.previous_start_of_word();
self.replace_text_in_range( self.replace_text_in_range(
Some(self.range_to_utf16(&(offset..self.cursor().offset))), Some(self.range_to_utf16(&(offset..self.cursor()))),
"", "",
window, window,
cx, cx,
@ -1282,7 +1215,7 @@ impl InputState {
) { ) {
let offset = self.next_end_of_word(); let offset = self.next_end_of_word();
self.replace_text_in_range( self.replace_text_in_range(
Some(self.range_to_utf16(&(self.cursor().offset..offset))), Some(self.range_to_utf16(&(self.cursor()..offset))),
"", "",
window, window,
cx, cx,
@ -1294,7 +1227,7 @@ impl InputState {
if self.mode.is_multi_line() { if self.mode.is_multi_line() {
// Get current line indent // Get current line indent
let indent = if self.mode.is_code_editor() { let indent = if self.mode.is_code_editor() {
self.indent_of_next_line(window, cx) self.indent_of_next_line()
} else { } else {
"".to_string() "".to_string()
}; };
@ -1359,7 +1292,7 @@ impl InputState {
let selected_text = self let selected_text = self
.text_for_range( .text_for_range(
self.range_to_utf16(&(offset..selected_range.end.offset)), self.range_to_utf16(&(offset..selected_range.end)),
&mut None, &mut None,
window, window,
cx, cx,
@ -1379,15 +1312,14 @@ impl InputState {
} }
if is_selected { if is_selected {
self.selected_range = self.selected_range = (start_offset..selected_range.end + added_len).into();
(Cursor::new(start_offset)..selected_range.end + added_len).into();
} else { } else {
self.selected_range = self.selected_range =
(selected_range.start + added_len..selected_range.end + added_len).into(); (selected_range.start + added_len..selected_range.end + added_len).into();
} }
} else { } else {
// Selected none // Selected none
let offset = self.selected_range.start.offset; let offset = self.selected_range.start;
self.replace_text_in_range( self.replace_text_in_range(
Some(self.range_to_utf16(&(offset..offset))), Some(self.range_to_utf16(&(offset..offset))),
&tab_indent, &tab_indent,
@ -1417,7 +1349,7 @@ impl InputState {
let selected_text = self let selected_text = self
.text_for_range( .text_for_range(
self.range_to_utf16(&(offset..selected_range.end.offset)), self.range_to_utf16(&(offset..selected_range.end)),
&mut None, &mut None,
window, window,
cx, cx,
@ -1442,14 +1374,12 @@ impl InputState {
} }
if is_selected { if is_selected {
self.selected_range = (Cursor::new(start_offset)
..Cursor::new(selected_range.end.saturating_sub(removed_len)))
.into();
} else {
self.selected_range = self.selected_range =
(Cursor::new(selected_range.start.saturating_sub(removed_len)) (start_offset..selected_range.end.saturating_sub(removed_len)).into();
..Cursor::new(selected_range.end.saturating_sub(removed_len))) } else {
.into(); self.selected_range = (selected_range.start.saturating_sub(removed_len)
..selected_range.end.saturating_sub(removed_len))
.into();
} }
} else { } else {
// Selected none // Selected none
@ -1470,7 +1400,7 @@ impl InputState {
); );
removed_len = tab_indent.len(); removed_len = tab_indent.len();
let new_offset = start_offset.saturating_sub(removed_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 { if event.modifiers.shift {
self.select_to(Cursor::new(offset), window, cx); self.select_to(offset, window, cx);
} else { } 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. /// The offset is the UTF-8 offset.
/// ///
/// Ensure the offset use self.next_boundary or self.previous_boundary to get the correct 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<Self>) { fn move_to(&mut self, offset: usize, _: &mut Window, cx: &mut Context<Self>) {
let cursor = Cursor::new(cursor.offset.clamp(0, self.text.len())); let offset = offset.clamp(0, self.text.len());
self.selected_range = (cursor..cursor).into(); self.selected_range = (offset..offset).into();
self.pause_blink_cursor(cx); self.pause_blink_cursor(cx);
self.update_preferred_column(); self.update_preferred_column();
cx.notify() cx.notify()
} }
/// Get the cursor position. /// Get byte offset of the cursor.
/// ///
/// The offset is the UTF-8 offset. /// 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 { if let Some(marked_range) = &self.marked_range {
return marked_range.end; return marked_range.end;
} }
@ -1814,12 +1744,12 @@ impl InputState {
/// The offset is the UTF-8 offset. /// The offset is the UTF-8 offset.
/// ///
/// Ensure the offset use self.next_boundary or self.previous_boundary to get the correct 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<Self>) { fn select_to(&mut self, offset: usize, _: &mut Window, cx: &mut Context<Self>) {
let offset = offset.clamp(0, self.text.len()); let offset = offset.clamp(0, self.text.len());
if self.selection_reversed { if self.selection_reversed {
self.selected_range.start = Cursor::new(offset) self.selected_range.start = offset
} else { } else {
self.selected_range.end = Cursor::new(offset) self.selected_range.end = offset
}; };
if self.selected_range.end < self.selected_range.start { if self.selected_range.end < self.selected_range.start {
@ -1900,7 +1830,7 @@ impl InputState {
/// Unselects the currently selected text. /// Unselects the currently selected text.
pub fn unselect(&mut self, _: &mut Window, cx: &mut Context<Self>) { pub fn unselect(&mut self, _: &mut Window, cx: &mut Context<Self>) {
let offset = self.cursor().offset; let offset = self.cursor();
self.selected_range = (offset..offset).into(); self.selected_range = (offset..offset).into();
cx.notify() cx.notify()
} }
@ -2022,7 +1952,7 @@ impl InputState {
} }
let offset = self.index_for_mouse_position(event.position, window, cx); 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<Self>) -> bool { fn is_valid_input(&self, new_text: &str, cx: &mut Context<Self>) -> bool {

View file

@ -7,12 +7,7 @@ use gpui::{
Window, Window,
}; };
use crate::{ use crate::{global_state::GlobalState, input::Selection, text::node::LinkMark, ActiveTheme};
global_state::GlobalState,
input::{Cursor, Selection},
text::node::LinkMark,
ActiveTheme,
};
/// A inline element used to render a inline text and support selectable. /// A inline element used to render a inline text and support selectable.
/// ///
@ -95,7 +90,7 @@ impl Inline {
text_layout: &TextLayout, text_layout: &TextLayout,
window: &mut Window, window: &mut Window,
cx: &mut App, cx: &mut App,
) -> (bool, bool, Option<(usize, usize)>) { ) -> (bool, bool, Option<Selection>) {
let Some(text_view_state) = GlobalState::global(cx).text_view_state() else { let Some(text_view_state) = GlobalState::global(cx).text_view_state() else {
return (false, false, None); return (false, false, None);
}; };
@ -112,7 +107,7 @@ impl Inline {
// Use for debug selection bounds // Use for debug selection bounds
// self.paint_selected_bounds(selection_bounds, window, cx); // self.paint_selected_bounds(selection_bounds, window, cx);
let mut selection = None; let mut selection: Option<Selection> = None;
let mut offset = 0; let mut offset = 0;
let mut chars = self.text.chars().peekable(); let mut chars = self.text.chars().peekable();
while let Some(c) = chars.next() { 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 point_in_text_selection(pos, char_width, &selection_bounds, line_height) {
if selection.is_none() { if selection.is_none() {
selection = Some((offset, offset)); selection = Some((offset..offset).into());
} }
let next_offset = offset + c.len_utf8(); 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(); offset += c.len_utf8();
@ -152,15 +147,15 @@ impl Inline {
window: &mut Window, window: &mut Window,
cx: &mut App, cx: &mut App,
) { ) {
let mut start_offset = selection.start.offset(); let mut start = selection.start;
let mut end_offset = selection.end.offset(); let mut end = selection.end;
if end_offset < start_offset { if end < start {
std::mem::swap(&mut start_offset, &mut end_offset); 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; 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; return;
}; };
@ -307,14 +302,7 @@ impl Element for Inline {
let (is_selectable, is_selection, selection) = let (is_selectable, is_selection, selection) =
self.layout_selections(&text_layout, window, cx); self.layout_selections(&text_layout, window, cx);
*state.selection.borrow_mut() = if let Some(selection) = selection { *state.selection.borrow_mut() = selection;
Some(Selection {
start: Cursor::new(selection.0),
end: Cursor::new(selection.1),
})
} else {
None
};
if is_selection || is_selectable { if is_selection || is_selectable {
window.set_cursor_style(CursorStyle::IBeam, &hitbox); window.set_cursor_style(CursorStyle::IBeam, &hitbox);

View file

@ -166,12 +166,12 @@ impl Paragraph {
for c in self.children.iter() { for c in self.children.iter() {
if let Some(selection) = c.state.selection.borrow().as_ref() { if let Some(selection) = c.state.selection.borrow().as_ref() {
let part_text = c.state.text.borrow().clone(); 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() { if let Some(selection) = self.state.selection.borrow().as_ref() {
let all_text = self.state.text.borrow().clone(); 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 text
@ -314,7 +314,7 @@ impl CodeBlock {
let mut text = String::new(); let mut text = String::new();
if let Some(selection) = self.state.selection.borrow().as_ref() { if let Some(selection) = self.state.selection.borrow().as_ref() {
let part_text = self.state.text.borrow().clone(); 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 text
} }