From 871577a20b1f85788762b1cb1fd5b524c5532f18 Mon Sep 17 00:00:00 2001 From: rhysd Date: Fri, 2 Aug 2024 18:24:31 +0900 Subject: [PATCH 1/3] shift cursor position by line number width on horizontal scroll --- src/widget.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/widget.rs b/src/widget.rs index 0901e61..4892207 100644 --- a/src/widget.rs +++ b/src/widget.rs @@ -81,6 +81,13 @@ impl Viewport { } impl<'a> TextArea<'a> { + fn lnum_width(&self) -> u8 { + if self.line_number_style().is_none() { + return 0; + } + num_digits(self.lines().len()) + 2 + } + #[inline] fn text_widget(&'a self, top_row: usize, height: usize) -> Text<'a> { let lines_len = self.lines().len(); @@ -122,7 +129,8 @@ impl Widget for &TextArea<'_> { let cursor = self.cursor(); let (top_row, top_col) = self.viewport.scroll_top(); let top_row = next_scroll_top(top_row, cursor.0 as u16, height); - let top_col = next_scroll_top(top_col, cursor.1 as u16, width); + let scroll_width = width.saturating_sub(self.lnum_width() as _); + let top_col = next_scroll_top(top_col, cursor.1 as u16, scroll_width); let (text, style) = if !self.placeholder.is_empty() && self.is_empty() { (self.placeholder_widget(), self.placeholder_style) From 4e1adc66a9e949a6c89834bbd390ee8f6ccff5cf Mon Sep 17 00:00:00 2001 From: rhysd Date: Fri, 2 Aug 2024 21:21:59 +0900 Subject: [PATCH 2/3] smoothly slide the line number part into screen on scrolling left --- src/widget.rs | 50 ++++++++++++++++++++++++++++++-------------------- 1 file changed, 30 insertions(+), 20 deletions(-) diff --git a/src/widget.rs b/src/widget.rs index 4892207..00aadc4 100644 --- a/src/widget.rs +++ b/src/widget.rs @@ -80,14 +80,18 @@ impl Viewport { } } -impl<'a> TextArea<'a> { - fn lnum_width(&self) -> u8 { - if self.line_number_style().is_none() { - return 0; - } - num_digits(self.lines().len()) + 2 +#[inline] +fn next_scroll_top(prev_top: u16, cursor: u16, len: u16) -> u16 { + if cursor < prev_top { + cursor + } else if prev_top + len <= cursor { + cursor + 1 - len + } else { + prev_top } +} +impl<'a> TextArea<'a> { #[inline] fn text_widget(&'a self, top_row: usize, height: usize) -> Text<'a> { let lines_len = self.lines().len(); @@ -106,6 +110,24 @@ impl<'a> TextArea<'a> { let text = Span::raw(self.placeholder.as_str()); Text::from(Line::from(vec![cursor, text])) } + + fn scroll_top_row(&self, prev_top: u16, height: u16) -> u16 { + next_scroll_top(prev_top, self.cursor().0 as u16, height) + } + + fn scroll_top_col(&self, prev_top: u16, width: u16) -> u16 { + let mut cursor = self.cursor().1 as u16; + if self.line_number_style().is_some() { + // Adjust the cursor position due to the width of line number. `+ 2` for margins + let lnum = num_digits(self.lines().len()) as u16 + 2; + if cursor <= lnum { + cursor *= 2; // Smoothly slide the line number into the screen on scrolling left + } else { + cursor += lnum; // The cursor position is shifted by the line number part + }; + } + next_scroll_top(prev_top, cursor, width) + } } impl Widget for &TextArea<'_> { @@ -116,21 +138,9 @@ impl Widget for &TextArea<'_> { area }; - fn next_scroll_top(prev_top: u16, cursor: u16, length: u16) -> u16 { - if cursor < prev_top { - cursor - } else if prev_top + length <= cursor { - cursor + 1 - length - } else { - prev_top - } - } - - let cursor = self.cursor(); let (top_row, top_col) = self.viewport.scroll_top(); - let top_row = next_scroll_top(top_row, cursor.0 as u16, height); - let scroll_width = width.saturating_sub(self.lnum_width() as _); - let top_col = next_scroll_top(top_col, cursor.1 as u16, scroll_width); + let top_row = self.scroll_top_row(top_row, height); + let top_col = self.scroll_top_col(top_col, width); let (text, style) = if !self.placeholder.is_empty() && self.is_empty() { (self.placeholder_widget(), self.placeholder_style) From 59ab95cc76009f9a6e048f333d2a99f0216572b0 Mon Sep 17 00:00:00 2001 From: rhysd Date: Fri, 2 Aug 2024 22:43:50 +0900 Subject: [PATCH 3/3] remove redundant `clone()` call with ratatui --- src/widget.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/widget.rs b/src/widget.rs index 00aadc4..6afd884 100644 --- a/src/widget.rs +++ b/src/widget.rs @@ -92,7 +92,6 @@ fn next_scroll_top(prev_top: u16, cursor: u16, len: u16) -> u16 { } impl<'a> TextArea<'a> { - #[inline] fn text_widget(&'a self, top_row: usize, height: usize) -> Text<'a> { let lines_len = self.lines().len(); let lnum_len = num_digits(lines_len); @@ -104,7 +103,6 @@ impl<'a> TextArea<'a> { Text::from(lines) } - #[inline] fn placeholder_widget(&'a self) -> Text<'a> { let cursor = Span::styled(" ", self.cursor_style); let text = Span::raw(self.placeholder.as_str()); @@ -156,7 +154,11 @@ impl Widget for &TextArea<'_> { .alignment(self.alignment()); if let Some(b) = self.block() { text_area = b.inner(area); - b.clone().render(area, buf) + // ratatui does not need `clone()` call because `Block` implements `WidgetRef` and `&T` implements `Widget` + // where `T: Widget`. So `b.render` internally calls `b.render_ref` and it doesn't move out `self`. + #[cfg(feature = "tuirs")] + let b = b.clone(); + b.render(area, buf) } if top_col != 0 { inner = inner.scroll((0, top_col));