1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
use crate::widget::Viewport;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
/// Specify how to scroll the textarea.
///
/// This type is marked as `#[non_exhaustive]` since more variations may be supported in the future. Note that the cursor will
/// not move until it goes out the viewport. See also: [`TextArea::scroll`]
///
/// [`TextArea::scroll`]: https://docs.rs/tui-textarea/latest/tui_textarea/struct.TextArea.html#method.scroll
#[non_exhaustive]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum Scrolling {
/// Scroll the textarea by rows (vertically) and columns (horizontally). Passing positive scroll amounts to `rows` and `cols`
/// scolls it to down and right. Negative integers means the opposite directions. `(i16, i16)` pair can be converted into
/// `Scrolling::Delta` where 1st element means rows and 2nd means columns.
///
/// ```
/// # use ratatui::buffer::Buffer;
/// # use ratatui::layout::Rect;
/// # use ratatui::widgets::Widget as _;
/// use tui_textarea::{TextArea, Scrolling};
///
/// // Let's say terminal height is 8.
///
/// // Create textarea with 20 lines "0", "1", "2", "3", ...
/// let mut textarea: TextArea = (0..20).into_iter().map(|i| i.to_string()).collect();
/// # // Call `render` at least once to populate terminal size
/// # let r = Rect { x: 0, y: 0, width: 24, height: 8 };
/// # let mut b = Buffer::empty(r.clone());
/// # textarea.render(r, &mut b);
///
/// // Scroll down by 2 lines.
/// textarea.scroll(Scrolling::Delta{rows: 2, cols: 0});
/// assert_eq!(textarea.cursor(), (2, 0));
///
/// // (1, 0) is converted into Scrolling::Delta{rows: 1, cols: 0}
/// textarea.scroll((1, 0));
/// assert_eq!(textarea.cursor(), (3, 0));
/// ```
Delta { rows: i16, cols: i16 },
/// Scroll down the textarea by one page.
///
/// ```
/// # use ratatui::buffer::Buffer;
/// # use ratatui::layout::Rect;
/// # use ratatui::widgets::Widget as _;
/// use tui_textarea::{TextArea, Scrolling};
///
/// // Let's say terminal height is 8.
///
/// // Create textarea with 20 lines "0", "1", "2", "3", ...
/// let mut textarea: TextArea = (0..20).into_iter().map(|i| i.to_string()).collect();
/// # // Call `render` at least once to populate terminal size
/// # let r = Rect { x: 0, y: 0, width: 24, height: 8 };
/// # let mut b = Buffer::empty(r.clone());
/// # textarea.render(r, &mut b);
///
/// // Scroll down by one page (8 lines)
/// textarea.scroll(Scrolling::PageDown);
/// assert_eq!(textarea.cursor(), (8, 0));
/// textarea.scroll(Scrolling::PageDown);
/// assert_eq!(textarea.cursor(), (16, 0));
/// textarea.scroll(Scrolling::PageDown);
/// assert_eq!(textarea.cursor(), (19, 0)); // Reached bottom of the textarea
/// ```
PageDown,
/// Scroll up the textarea by one page.
///
/// ```
/// # use ratatui::buffer::Buffer;
/// # use ratatui::layout::Rect;
/// # use ratatui::widgets::Widget as _;
/// use tui_textarea::{TextArea, Scrolling, CursorMove};
///
/// // Let's say terminal height is 8.
///
/// // Create textarea with 20 lines "0", "1", "2", "3", ...
/// let mut textarea: TextArea = (0..20).into_iter().map(|i| i.to_string()).collect();
/// # // Call `render` at least once to populate terminal size
/// # let r = Rect { x: 0, y: 0, width: 24, height: 8 };
/// # let mut b = Buffer::empty(r.clone());
/// # textarea.render(r.clone(), &mut b);
///
/// // Go to the last line at first
/// textarea.move_cursor(CursorMove::Bottom);
/// assert_eq!(textarea.cursor(), (19, 0));
/// # // Call `render` to populate terminal size
/// # textarea.render(r.clone(), &mut b);
///
/// // Scroll up by one page (8 lines)
/// textarea.scroll(Scrolling::PageUp);
/// assert_eq!(textarea.cursor(), (11, 0));
/// textarea.scroll(Scrolling::PageUp);
/// assert_eq!(textarea.cursor(), (7, 0)); // Reached top of the textarea
/// ```
PageUp,
/// Scroll down the textarea by half of the page.
///
/// ```
/// # use ratatui::buffer::Buffer;
/// # use ratatui::layout::Rect;
/// # use ratatui::widgets::Widget as _;
/// use tui_textarea::{TextArea, Scrolling};
///
/// // Let's say terminal height is 8.
///
/// // Create textarea with 10 lines "0", "1", "2", "3", ...
/// let mut textarea: TextArea = (0..10).into_iter().map(|i| i.to_string()).collect();
/// # // Call `render` at least once to populate terminal size
/// # let r = Rect { x: 0, y: 0, width: 24, height: 8 };
/// # let mut b = Buffer::empty(r.clone());
/// # textarea.render(r, &mut b);
///
/// // Scroll down by half-page (4 lines)
/// textarea.scroll(Scrolling::HalfPageDown);
/// assert_eq!(textarea.cursor(), (4, 0));
/// textarea.scroll(Scrolling::HalfPageDown);
/// assert_eq!(textarea.cursor(), (8, 0));
/// textarea.scroll(Scrolling::HalfPageDown);
/// assert_eq!(textarea.cursor(), (9, 0)); // Reached bottom of the textarea
/// ```
HalfPageDown,
/// Scroll up the textarea by half of the page.
///
/// ```
/// # use ratatui::buffer::Buffer;
/// # use ratatui::layout::Rect;
/// # use ratatui::widgets::Widget as _;
/// use tui_textarea::{TextArea, Scrolling, CursorMove};
///
/// // Let's say terminal height is 8.
///
/// // Create textarea with 20 lines "0", "1", "2", "3", ...
/// let mut textarea: TextArea = (0..20).into_iter().map(|i| i.to_string()).collect();
/// # // Call `render` at least once to populate terminal size
/// # let r = Rect { x: 0, y: 0, width: 24, height: 8 };
/// # let mut b = Buffer::empty(r.clone());
/// # textarea.render(r.clone(), &mut b);
///
/// // Go to the last line at first
/// textarea.move_cursor(CursorMove::Bottom);
/// assert_eq!(textarea.cursor(), (19, 0));
/// # // Call `render` to populate terminal size
/// # textarea.render(r.clone(), &mut b);
///
/// // Scroll up by half-page (4 lines)
/// textarea.scroll(Scrolling::HalfPageUp);
/// assert_eq!(textarea.cursor(), (15, 0));
/// textarea.scroll(Scrolling::HalfPageUp);
/// assert_eq!(textarea.cursor(), (11, 0));
/// ```
HalfPageUp,
}
impl Scrolling {
pub(crate) fn scroll(self, viewport: &mut Viewport) {
let (rows, cols) = match self {
Self::Delta { rows, cols } => (rows, cols),
Self::PageDown => {
let (_, _, _, height) = viewport.rect();
(height as i16, 0)
}
Self::PageUp => {
let (_, _, _, height) = viewport.rect();
(-(height as i16), 0)
}
Self::HalfPageDown => {
let (_, _, _, height) = viewport.rect();
((height as i16) / 2, 0)
}
Self::HalfPageUp => {
let (_, _, _, height) = viewport.rect();
(-(height as i16) / 2, 0)
}
};
viewport.scroll(rows, cols);
}
}
impl From<(i16, i16)> for Scrolling {
fn from((rows, cols): (i16, i16)) -> Self {
Self::Delta { rows, cols }
}
}
#[cfg(test)]
mod tests {
use super::*;
// Separate tests for tui-rs support
#[test]
fn delta() {
use crate::ratatui::buffer::Buffer;
use crate::ratatui::layout::Rect;
use crate::ratatui::widgets::Widget as _;
use crate::TextArea;
let mut textarea: TextArea = (0..20).map(|i| i.to_string()).collect();
let r = Rect {
x: 0,
y: 0,
width: 24,
height: 8,
};
let mut b = Buffer::empty(r);
textarea.render(r, &mut b);
textarea.scroll(Scrolling::Delta { rows: 2, cols: 0 });
assert_eq!(textarea.cursor(), (2, 0));
textarea.scroll((1, 0));
assert_eq!(textarea.cursor(), (3, 0));
}
}