зеркало из
https://github.com/glebtv/tui-textarea.git
synced 2026-09-08 00:15:50 +03:00
return if text was modified or not from methods to edit text
Этот коммит содержится в:
12
README.md
12
README.md
@@ -274,7 +274,9 @@ loop {
|
|||||||
match Input::from(read()?) {
|
match Input::from(read()?) {
|
||||||
Input { key: Key::Char('m'), ctrl: true, alt: false }
|
Input { key: Key::Char('m'), ctrl: true, alt: false }
|
||||||
| Input { key: Key::Enter, .. } => continue,
|
| Input { key: Key::Enter, .. } => continue,
|
||||||
input => textarea.input(key),
|
input => {
|
||||||
|
textarea.input(key);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -347,8 +349,12 @@ loop {
|
|||||||
_ => {},
|
_ => {},
|
||||||
},
|
},
|
||||||
Mode::Insert => match Input::from(read()?) {
|
Mode::Insert => match Input::from(read()?) {
|
||||||
Input { key: Key::Esc, .. } => mode = Mode::Normal,
|
Input { key: Key::Esc, .. } => {
|
||||||
input => textarea.input(input), // Use default key mappings in insert mode
|
mode = Mode::Normal;
|
||||||
|
}
|
||||||
|
input => {
|
||||||
|
textarea.input(input); // Use default key mappings in insert mode
|
||||||
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ macro_rules! error {
|
|||||||
struct Buffer<'a> {
|
struct Buffer<'a> {
|
||||||
textarea: TextArea<'a>,
|
textarea: TextArea<'a>,
|
||||||
path: PathBuf,
|
path: PathBuf,
|
||||||
|
modified: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Buffer<'a> {
|
impl<'a> Buffer<'a> {
|
||||||
@@ -44,15 +45,23 @@ impl<'a> Buffer<'a> {
|
|||||||
TextArea::default()
|
TextArea::default()
|
||||||
};
|
};
|
||||||
textarea.set_line_number_style(Style::default().fg(Color::DarkGray));
|
textarea.set_line_number_style(Style::default().fg(Color::DarkGray));
|
||||||
Ok(Self { textarea, path })
|
Ok(Self {
|
||||||
|
textarea,
|
||||||
|
path,
|
||||||
|
modified: false,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn save(&self) -> io::Result<()> {
|
fn save(&mut self) -> io::Result<()> {
|
||||||
|
if !self.modified {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
let mut f = io::BufWriter::new(fs::File::create(&self.path)?);
|
let mut f = io::BufWriter::new(fs::File::create(&self.path)?);
|
||||||
for line in self.textarea.lines() {
|
for line in self.textarea.lines() {
|
||||||
f.write_all(line.as_bytes())?;
|
f.write_all(line.as_bytes())?;
|
||||||
f.write_all(b"\n")?;
|
f.write_all(b"\n")?;
|
||||||
}
|
}
|
||||||
|
self.modified = false;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -111,11 +120,13 @@ impl<'a> Editor<'a> {
|
|||||||
let widget = textarea.widget();
|
let widget = textarea.widget();
|
||||||
f.render_widget(widget, chunks[0]);
|
f.render_widget(widget, chunks[0]);
|
||||||
|
|
||||||
|
let modified = if buffer.modified { " [modified]" } else { "" };
|
||||||
let status = format!(
|
let status = format!(
|
||||||
"[{}/{}] {:?}",
|
"[{}/{}] {:?}{}",
|
||||||
self.current + 1,
|
self.current + 1,
|
||||||
self.buffers.len(),
|
self.buffers.len(),
|
||||||
buffer.path,
|
buffer.path,
|
||||||
|
modified,
|
||||||
);
|
);
|
||||||
let status =
|
let status =
|
||||||
Paragraph::new(status).style(Style::default().add_modifier(Modifier::REVERSED));
|
Paragraph::new(status).style(Style::default().add_modifier(Modifier::REVERSED));
|
||||||
@@ -161,7 +172,10 @@ impl<'a> Editor<'a> {
|
|||||||
self.buffers[self.current].save()?;
|
self.buffers[self.current].save()?;
|
||||||
self.message = Some("Saved!");
|
self.message = Some("Saved!");
|
||||||
}
|
}
|
||||||
input => self.buffers[self.current].textarea.input(input),
|
input => {
|
||||||
|
let buffer = &mut self.buffers[self.current];
|
||||||
|
buffer.modified = buffer.textarea.input(input);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -54,7 +54,9 @@ fn main() -> Result<(), Box<dyn Error>> {
|
|||||||
loop {
|
loop {
|
||||||
match events.recv()? {
|
match events.recv()? {
|
||||||
Event::Key(Key::Esc) => break,
|
Event::Key(Key::Esc) => break,
|
||||||
Event::Key(key) => textarea.input(key),
|
Event::Key(key) => {
|
||||||
|
textarea.input(key);
|
||||||
|
}
|
||||||
Event::Tick => {}
|
Event::Tick => {}
|
||||||
}
|
}
|
||||||
term.draw(|f| {
|
term.draw(|f| {
|
||||||
|
|||||||
230
src/textarea.rs
230
src/textarea.rs
@@ -97,14 +97,15 @@ impl<'a, S: Into<String>> FromIterator<S> for TextArea<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create [`TextArea`] instance with empty text content.
|
||||||
|
/// ```
|
||||||
|
/// use tui_textarea::TextArea;
|
||||||
|
///
|
||||||
|
/// let textarea = TextArea::default();
|
||||||
|
/// assert_eq!(textarea.lines(), [""]);
|
||||||
|
/// assert!(textarea.is_empty());
|
||||||
|
/// ```
|
||||||
impl<'a> Default for TextArea<'a> {
|
impl<'a> Default for TextArea<'a> {
|
||||||
/// Create [`TextArea`] instance with empty text content.
|
|
||||||
/// ```
|
|
||||||
/// use tui_textarea::TextArea;
|
|
||||||
///
|
|
||||||
/// let textarea = TextArea::default();
|
|
||||||
/// assert_eq!(textarea.lines(), [""]);
|
|
||||||
/// ```
|
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::new(vec![String::new()])
|
Self::new(vec![String::new()])
|
||||||
}
|
}
|
||||||
@@ -143,6 +144,7 @@ impl<'a> TextArea<'a> {
|
|||||||
/// [the module document](./index.html).
|
/// [the module document](./index.html).
|
||||||
/// `crossterm` and `termion` features enable conversion from their own key event types into [`Input`] so this
|
/// `crossterm` and `termion` features enable conversion from their own key event types into [`Input`] so this
|
||||||
/// method can take the event values directly.
|
/// method can take the event values directly.
|
||||||
|
/// This method returns if the input modified text contents or not in the textarea.
|
||||||
/// ```ignore
|
/// ```ignore
|
||||||
/// use tui_textarea::{TextArea, Key, Input};
|
/// use tui_textarea::{TextArea, Key, Input};
|
||||||
///
|
///
|
||||||
@@ -164,16 +166,20 @@ impl<'a> TextArea<'a> {
|
|||||||
///
|
///
|
||||||
/// // Handle backend-agnostic key input
|
/// // Handle backend-agnostic key input
|
||||||
/// let input = Input { key: Key::Char('a'), ctrl: false, alt: false };
|
/// let input = Input { key: Key::Char('a'), ctrl: false, alt: false };
|
||||||
/// textarea.input(input);
|
/// let modified = textarea.input(input);
|
||||||
|
/// assert!(modified);
|
||||||
/// ```
|
/// ```
|
||||||
pub fn input(&mut self, input: impl Into<Input>) {
|
pub fn input(&mut self, input: impl Into<Input>) -> bool {
|
||||||
let input = input.into();
|
let input = input.into();
|
||||||
match input {
|
let modified = match input {
|
||||||
Input {
|
Input {
|
||||||
key: Key::Char(c),
|
key: Key::Char(c),
|
||||||
ctrl: false,
|
ctrl: false,
|
||||||
alt: false,
|
alt: false,
|
||||||
} => self.insert_char(c),
|
} => {
|
||||||
|
self.insert_char(c);
|
||||||
|
true
|
||||||
|
}
|
||||||
Input {
|
Input {
|
||||||
key: Key::Tab,
|
key: Key::Tab,
|
||||||
ctrl: false,
|
ctrl: false,
|
||||||
@@ -206,7 +212,10 @@ impl<'a> TextArea<'a> {
|
|||||||
}
|
}
|
||||||
| Input {
|
| Input {
|
||||||
key: Key::Enter, ..
|
key: Key::Enter, ..
|
||||||
} => self.insert_newline(),
|
} => {
|
||||||
|
self.insert_newline();
|
||||||
|
true
|
||||||
|
}
|
||||||
Input {
|
Input {
|
||||||
key: Key::Char('k'),
|
key: Key::Char('k'),
|
||||||
ctrl: true,
|
ctrl: true,
|
||||||
@@ -251,7 +260,10 @@ impl<'a> TextArea<'a> {
|
|||||||
key: Key::Down,
|
key: Key::Down,
|
||||||
ctrl: false,
|
ctrl: false,
|
||||||
alt: false,
|
alt: false,
|
||||||
} => self.move_cursor(CursorMove::Down),
|
} => {
|
||||||
|
self.move_cursor(CursorMove::Down);
|
||||||
|
false
|
||||||
|
}
|
||||||
Input {
|
Input {
|
||||||
key: Key::Char('p'),
|
key: Key::Char('p'),
|
||||||
ctrl: true,
|
ctrl: true,
|
||||||
@@ -261,7 +273,10 @@ impl<'a> TextArea<'a> {
|
|||||||
key: Key::Up,
|
key: Key::Up,
|
||||||
ctrl: false,
|
ctrl: false,
|
||||||
alt: false,
|
alt: false,
|
||||||
} => self.move_cursor(CursorMove::Up),
|
} => {
|
||||||
|
self.move_cursor(CursorMove::Up);
|
||||||
|
false
|
||||||
|
}
|
||||||
Input {
|
Input {
|
||||||
key: Key::Char('f'),
|
key: Key::Char('f'),
|
||||||
ctrl: true,
|
ctrl: true,
|
||||||
@@ -271,7 +286,10 @@ impl<'a> TextArea<'a> {
|
|||||||
key: Key::Right,
|
key: Key::Right,
|
||||||
ctrl: false,
|
ctrl: false,
|
||||||
alt: false,
|
alt: false,
|
||||||
} => self.move_cursor(CursorMove::Forward),
|
} => {
|
||||||
|
self.move_cursor(CursorMove::Forward);
|
||||||
|
false
|
||||||
|
}
|
||||||
Input {
|
Input {
|
||||||
key: Key::Char('b'),
|
key: Key::Char('b'),
|
||||||
ctrl: true,
|
ctrl: true,
|
||||||
@@ -281,7 +299,10 @@ impl<'a> TextArea<'a> {
|
|||||||
key: Key::Left,
|
key: Key::Left,
|
||||||
ctrl: false,
|
ctrl: false,
|
||||||
alt: false,
|
alt: false,
|
||||||
} => self.move_cursor(CursorMove::Back),
|
} => {
|
||||||
|
self.move_cursor(CursorMove::Back);
|
||||||
|
false
|
||||||
|
}
|
||||||
Input {
|
Input {
|
||||||
key: Key::Char('a'),
|
key: Key::Char('a'),
|
||||||
ctrl: true,
|
ctrl: true,
|
||||||
@@ -292,7 +313,10 @@ impl<'a> TextArea<'a> {
|
|||||||
key: Key::Left | Key::Char('b'),
|
key: Key::Left | Key::Char('b'),
|
||||||
ctrl: true,
|
ctrl: true,
|
||||||
alt: true,
|
alt: true,
|
||||||
} => self.move_cursor(CursorMove::Head),
|
} => {
|
||||||
|
self.move_cursor(CursorMove::Head);
|
||||||
|
false
|
||||||
|
}
|
||||||
Input {
|
Input {
|
||||||
key: Key::Char('e'),
|
key: Key::Char('e'),
|
||||||
ctrl: true,
|
ctrl: true,
|
||||||
@@ -303,7 +327,10 @@ impl<'a> TextArea<'a> {
|
|||||||
key: Key::Right | Key::Char('f'),
|
key: Key::Right | Key::Char('f'),
|
||||||
ctrl: true,
|
ctrl: true,
|
||||||
alt: true,
|
alt: true,
|
||||||
} => self.move_cursor(CursorMove::End),
|
} => {
|
||||||
|
self.move_cursor(CursorMove::End);
|
||||||
|
false
|
||||||
|
}
|
||||||
Input {
|
Input {
|
||||||
key: Key::Char('<'),
|
key: Key::Char('<'),
|
||||||
ctrl: false,
|
ctrl: false,
|
||||||
@@ -313,7 +340,10 @@ impl<'a> TextArea<'a> {
|
|||||||
key: Key::Up | Key::Char('p'),
|
key: Key::Up | Key::Char('p'),
|
||||||
ctrl: true,
|
ctrl: true,
|
||||||
alt: true,
|
alt: true,
|
||||||
} => self.move_cursor(CursorMove::Top),
|
} => {
|
||||||
|
self.move_cursor(CursorMove::Top);
|
||||||
|
false
|
||||||
|
}
|
||||||
Input {
|
Input {
|
||||||
key: Key::Char('>'),
|
key: Key::Char('>'),
|
||||||
ctrl: false,
|
ctrl: false,
|
||||||
@@ -323,7 +353,10 @@ impl<'a> TextArea<'a> {
|
|||||||
key: Key::Down | Key::Char('n'),
|
key: Key::Down | Key::Char('n'),
|
||||||
ctrl: true,
|
ctrl: true,
|
||||||
alt: true,
|
alt: true,
|
||||||
} => self.move_cursor(CursorMove::Bottom),
|
} => {
|
||||||
|
self.move_cursor(CursorMove::Bottom);
|
||||||
|
false
|
||||||
|
}
|
||||||
Input {
|
Input {
|
||||||
key: Key::Char('f'),
|
key: Key::Char('f'),
|
||||||
ctrl: false,
|
ctrl: false,
|
||||||
@@ -333,7 +366,10 @@ impl<'a> TextArea<'a> {
|
|||||||
key: Key::Right,
|
key: Key::Right,
|
||||||
ctrl: true,
|
ctrl: true,
|
||||||
alt: false,
|
alt: false,
|
||||||
} => self.move_cursor(CursorMove::WordForward),
|
} => {
|
||||||
|
self.move_cursor(CursorMove::WordForward);
|
||||||
|
false
|
||||||
|
}
|
||||||
Input {
|
Input {
|
||||||
key: Key::Char('b'),
|
key: Key::Char('b'),
|
||||||
ctrl: false,
|
ctrl: false,
|
||||||
@@ -343,7 +379,10 @@ impl<'a> TextArea<'a> {
|
|||||||
key: Key::Left,
|
key: Key::Left,
|
||||||
ctrl: true,
|
ctrl: true,
|
||||||
alt: false,
|
alt: false,
|
||||||
} => self.move_cursor(CursorMove::WordBack),
|
} => {
|
||||||
|
self.move_cursor(CursorMove::WordBack);
|
||||||
|
false
|
||||||
|
}
|
||||||
Input {
|
Input {
|
||||||
key: Key::Char('n'),
|
key: Key::Char('n'),
|
||||||
ctrl: false,
|
ctrl: false,
|
||||||
@@ -356,7 +395,10 @@ impl<'a> TextArea<'a> {
|
|||||||
}
|
}
|
||||||
| Input {
|
| Input {
|
||||||
key: Key::PageDown, ..
|
key: Key::PageDown, ..
|
||||||
} => self.move_cursor(CursorMove::ParagraphForward),
|
} => {
|
||||||
|
self.move_cursor(CursorMove::ParagraphForward);
|
||||||
|
false
|
||||||
|
}
|
||||||
Input {
|
Input {
|
||||||
key: Key::Char('p'),
|
key: Key::Char('p'),
|
||||||
ctrl: false,
|
ctrl: false,
|
||||||
@@ -369,28 +411,27 @@ impl<'a> TextArea<'a> {
|
|||||||
}
|
}
|
||||||
| Input {
|
| Input {
|
||||||
key: Key::PageUp, ..
|
key: Key::PageUp, ..
|
||||||
} => self.move_cursor(CursorMove::ParagraphBack),
|
} => {
|
||||||
|
self.move_cursor(CursorMove::ParagraphBack);
|
||||||
|
false
|
||||||
|
}
|
||||||
Input {
|
Input {
|
||||||
key: Key::Char('u'),
|
key: Key::Char('u'),
|
||||||
ctrl: true,
|
ctrl: true,
|
||||||
alt: false,
|
alt: false,
|
||||||
} => {
|
} => self.undo(),
|
||||||
self.undo();
|
|
||||||
}
|
|
||||||
Input {
|
Input {
|
||||||
key: Key::Char('r'),
|
key: Key::Char('r'),
|
||||||
ctrl: true,
|
ctrl: true,
|
||||||
alt: false,
|
alt: false,
|
||||||
} => {
|
} => self.redo(),
|
||||||
self.redo();
|
|
||||||
}
|
|
||||||
Input {
|
Input {
|
||||||
key: Key::Char('y' | 'v'),
|
key: Key::Char('y' | 'v'),
|
||||||
ctrl: true,
|
ctrl: true,
|
||||||
alt: false,
|
alt: false,
|
||||||
} => self.paste(),
|
} => self.paste(),
|
||||||
_ => {}
|
_ => false,
|
||||||
}
|
};
|
||||||
|
|
||||||
// Check invariants
|
// Check invariants
|
||||||
debug_assert!(!self.lines.is_empty(), "no line after {:?}", input);
|
debug_assert!(!self.lines.is_empty(), "no line after {:?}", input);
|
||||||
@@ -410,6 +451,8 @@ impl<'a> TextArea<'a> {
|
|||||||
self.lines[r],
|
self.lines[r],
|
||||||
input,
|
input,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
modified
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handle a key input without default key mappings. This method handles only
|
/// Handle a key input without default key mappings. This method handles only
|
||||||
@@ -419,16 +462,20 @@ impl<'a> TextArea<'a> {
|
|||||||
/// - Backspace
|
/// - Backspace
|
||||||
/// - Delete
|
/// - Delete
|
||||||
///
|
///
|
||||||
|
/// This method returns if the input modified text contents or not in the textarea.
|
||||||
|
///
|
||||||
/// This method is useful when you want to define your own key mappings and don't want default key mappings.
|
/// This method is useful when you want to define your own key mappings and don't want default key mappings.
|
||||||
/// See 'Define your own key mappings' section in [the module document](./index.html).
|
/// See 'Define your own key mappings' section in [the module document](./index.html).
|
||||||
pub fn input_without_shortcuts(&mut self, input: impl Into<Input>) {
|
pub fn input_without_shortcuts(&mut self, input: impl Into<Input>) -> bool {
|
||||||
let input = input.into();
|
match input.into() {
|
||||||
match input {
|
|
||||||
Input {
|
Input {
|
||||||
key: Key::Char(c),
|
key: Key::Char(c),
|
||||||
ctrl: false,
|
ctrl: false,
|
||||||
alt: false,
|
alt: false,
|
||||||
} => self.insert_char(c),
|
} => {
|
||||||
|
self.insert_char(c);
|
||||||
|
true
|
||||||
|
}
|
||||||
Input {
|
Input {
|
||||||
key: Key::Tab,
|
key: Key::Tab,
|
||||||
ctrl: false,
|
ctrl: false,
|
||||||
@@ -443,8 +490,11 @@ impl<'a> TextArea<'a> {
|
|||||||
} => self.delete_next_char(),
|
} => self.delete_next_char(),
|
||||||
Input {
|
Input {
|
||||||
key: Key::Enter, ..
|
key: Key::Enter, ..
|
||||||
} => self.insert_newline(),
|
} => {
|
||||||
_ => {}
|
self.insert_newline();
|
||||||
|
true
|
||||||
|
}
|
||||||
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -475,7 +525,8 @@ impl<'a> TextArea<'a> {
|
|||||||
self.push_history(EditKind::InsertChar(c, i), (row, col));
|
self.push_history(EditKind::InsertChar(c, i), (row, col));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Insert a string at current cursor position. Currently the string must not contain any newlines.
|
/// Insert a string at current cursor position. Currently the string must not contain any newlines. This method
|
||||||
|
/// returns if some text was inserted or not in the textarea.
|
||||||
/// ```
|
/// ```
|
||||||
/// use tui_textarea::TextArea;
|
/// use tui_textarea::TextArea;
|
||||||
///
|
///
|
||||||
@@ -484,10 +535,10 @@ impl<'a> TextArea<'a> {
|
|||||||
/// textarea.insert_str("hello");
|
/// textarea.insert_str("hello");
|
||||||
/// assert_eq!(textarea.lines(), ["hello"]);
|
/// assert_eq!(textarea.lines(), ["hello"]);
|
||||||
/// ```
|
/// ```
|
||||||
pub fn insert_str<S: Into<String>>(&mut self, s: S) {
|
pub fn insert_str<S: Into<String>>(&mut self, s: S) -> bool {
|
||||||
let s = s.into();
|
let s = s.into();
|
||||||
if s.is_empty() {
|
if s.is_empty() {
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
let (row, col) = self.cursor;
|
let (row, col) = self.cursor;
|
||||||
@@ -507,10 +558,11 @@ impl<'a> TextArea<'a> {
|
|||||||
|
|
||||||
self.cursor.1 += s.chars().count();
|
self.cursor.1 += s.chars().count();
|
||||||
self.push_history(EditKind::Insert(s, i), (row, col));
|
self.push_history(EditKind::Insert(s, i), (row, col));
|
||||||
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Delete a string in current cursor line. The `chars` parameter means number of characters, not a byte length of
|
/// Delete a string in current cursor line. The `chars` parameter means number of characters, not a byte length of
|
||||||
/// the string.
|
/// the string. This method returns if some text was deleted or not in the textarea.
|
||||||
/// ```
|
/// ```
|
||||||
/// use tui_textarea::TextArea;
|
/// use tui_textarea::TextArea;
|
||||||
///
|
///
|
||||||
@@ -519,9 +571,9 @@ impl<'a> TextArea<'a> {
|
|||||||
/// textarea.delete_str(1, 2);
|
/// textarea.delete_str(1, 2);
|
||||||
/// assert_eq!(textarea.lines(), ["🐱🐮"]);
|
/// assert_eq!(textarea.lines(), ["🐱🐮"]);
|
||||||
/// ```
|
/// ```
|
||||||
pub fn delete_str(&mut self, col: usize, chars: usize) {
|
pub fn delete_str(&mut self, col: usize, chars: usize) -> bool {
|
||||||
if chars == 0 {
|
if chars == 0 {
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
let row = self.cursor.0;
|
let row = self.cursor.0;
|
||||||
@@ -538,10 +590,15 @@ impl<'a> TextArea<'a> {
|
|||||||
self.cursor = (row, col);
|
self.cursor = (row, col);
|
||||||
self.push_history(EditKind::Remove(removed.clone(), i), (row, col));
|
self.push_history(EditKind::Remove(removed.clone(), i), (row, col));
|
||||||
self.yank = removed;
|
self.yank = removed;
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Insert a tab at current cursor position.
|
/// Insert a tab at current cursor position. Note that this method does nothing when the tab length is 0. This
|
||||||
|
/// method returns if a tab string was inserted or not in the textarea.
|
||||||
|
/// textarea.
|
||||||
/// ```
|
/// ```
|
||||||
/// use tui_textarea::TextArea;
|
/// use tui_textarea::TextArea;
|
||||||
///
|
///
|
||||||
@@ -550,11 +607,12 @@ impl<'a> TextArea<'a> {
|
|||||||
/// textarea.insert_tab();
|
/// textarea.insert_tab();
|
||||||
/// assert_eq!(textarea.lines(), [" hi"]);
|
/// assert_eq!(textarea.lines(), [" hi"]);
|
||||||
/// ```
|
/// ```
|
||||||
pub fn insert_tab(&mut self) {
|
pub fn insert_tab(&mut self) -> bool {
|
||||||
if self.tab_len > 0 {
|
if self.tab_len == 0 {
|
||||||
let len = self.tab_len as usize - self.cursor.1 % self.tab_len as usize;
|
return false;
|
||||||
self.insert_str(spaces(len));
|
|
||||||
}
|
}
|
||||||
|
let len = self.tab_len as usize - self.cursor.1 % self.tab_len as usize;
|
||||||
|
self.insert_str(spaces(len))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Insert a newline at current cursor position.
|
/// Insert a newline at current cursor position.
|
||||||
@@ -583,7 +641,8 @@ impl<'a> TextArea<'a> {
|
|||||||
self.push_history(EditKind::InsertNewline(idx), (row, col));
|
self.push_history(EditKind::InsertNewline(idx), (row, col));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Delete a newline from **head** of current cursor line.
|
/// Delete a newline from **head** of current cursor line. This method returns if a newline was deleted or not in
|
||||||
|
/// the textarea.
|
||||||
/// ```
|
/// ```
|
||||||
/// use tui_textarea::{TextArea, CursorMove};
|
/// use tui_textarea::{TextArea, CursorMove};
|
||||||
///
|
///
|
||||||
@@ -593,10 +652,10 @@ impl<'a> TextArea<'a> {
|
|||||||
/// textarea.delete_newline();
|
/// textarea.delete_newline();
|
||||||
/// assert_eq!(textarea.lines(), ["helloworld"]);
|
/// assert_eq!(textarea.lines(), ["helloworld"]);
|
||||||
/// ```
|
/// ```
|
||||||
pub fn delete_newline(&mut self) {
|
pub fn delete_newline(&mut self) -> bool {
|
||||||
let (row, col) = self.cursor;
|
let (row, col) = self.cursor;
|
||||||
if row == 0 {
|
if row == 0 {
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
let line = self.lines.remove(row);
|
let line = self.lines.remove(row);
|
||||||
@@ -606,10 +665,11 @@ impl<'a> TextArea<'a> {
|
|||||||
self.cursor = (row - 1, prev_line.chars().count());
|
self.cursor = (row - 1, prev_line.chars().count());
|
||||||
prev_line.push_str(&line);
|
prev_line.push_str(&line);
|
||||||
self.push_history(EditKind::DeleteNewline(prev_line_end), (row, col));
|
self.push_history(EditKind::DeleteNewline(prev_line_end), (row, col));
|
||||||
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Delete one character before cursor. When the cursor is at head of line, the newline before the cursor will be
|
/// Delete one character before cursor. When the cursor is at head of line, the newline before the cursor will be
|
||||||
/// removed.
|
/// removed. This method returns if some text was deleted or not in the textarea.
|
||||||
/// ```
|
/// ```
|
||||||
/// use tui_textarea::{TextArea, CursorMove};
|
/// use tui_textarea::{TextArea, CursorMove};
|
||||||
///
|
///
|
||||||
@@ -619,11 +679,10 @@ impl<'a> TextArea<'a> {
|
|||||||
/// textarea.delete_char();
|
/// textarea.delete_char();
|
||||||
/// assert_eq!(textarea.lines(), ["bc"]);
|
/// assert_eq!(textarea.lines(), ["bc"]);
|
||||||
/// ```
|
/// ```
|
||||||
pub fn delete_char(&mut self) {
|
pub fn delete_char(&mut self) -> bool {
|
||||||
let (row, col) = self.cursor;
|
let (row, col) = self.cursor;
|
||||||
if col == 0 {
|
if col == 0 {
|
||||||
self.delete_newline();
|
return self.delete_newline();
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let line = &mut self.lines[row];
|
let line = &mut self.lines[row];
|
||||||
@@ -631,11 +690,14 @@ impl<'a> TextArea<'a> {
|
|||||||
line.remove(i);
|
line.remove(i);
|
||||||
self.cursor.1 -= 1;
|
self.cursor.1 -= 1;
|
||||||
self.push_history(EditKind::DeleteChar(c, i), (row, col));
|
self.push_history(EditKind::DeleteChar(c, i), (row, col));
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Delete one character next to cursor. When the cursor is at end of line, the newline next to the cursor will be
|
/// Delete one character next to cursor. When the cursor is at end of line, the newline next to the cursor will be
|
||||||
/// removed.
|
/// removed. This method returns if a character was deleted or not in the textarea.
|
||||||
/// ```
|
/// ```
|
||||||
/// use tui_textarea::{TextArea, CursorMove};
|
/// use tui_textarea::{TextArea, CursorMove};
|
||||||
///
|
///
|
||||||
@@ -645,17 +707,17 @@ impl<'a> TextArea<'a> {
|
|||||||
/// textarea.delete_next_char();
|
/// textarea.delete_next_char();
|
||||||
/// assert_eq!(textarea.lines(), ["ac"]);
|
/// assert_eq!(textarea.lines(), ["ac"]);
|
||||||
/// ```
|
/// ```
|
||||||
pub fn delete_next_char(&mut self) {
|
pub fn delete_next_char(&mut self) -> bool {
|
||||||
let before = self.cursor;
|
let before = self.cursor;
|
||||||
self.move_cursor(CursorMove::Forward);
|
self.move_cursor(CursorMove::Forward);
|
||||||
if before == self.cursor {
|
if before == self.cursor {
|
||||||
return; // Cursor didn't move, meant no character at next of cursor.
|
return false; // Cursor didn't move, meant no character at next of cursor.
|
||||||
}
|
}
|
||||||
self.delete_char();
|
self.delete_char()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Delete string from cursor to end of the line. When the cursor is at end of line, the newline next to the cursor
|
/// Delete string from cursor to end of the line. When the cursor is at end of line, the newline next to the cursor
|
||||||
/// will be removed.
|
/// will be removed. This method returns if some text was deleted or not in the textarea.
|
||||||
/// ```
|
/// ```
|
||||||
/// use tui_textarea::{TextArea, CursorMove};
|
/// use tui_textarea::{TextArea, CursorMove};
|
||||||
///
|
///
|
||||||
@@ -668,12 +730,12 @@ impl<'a> TextArea<'a> {
|
|||||||
/// textarea.delete_line_by_end();
|
/// textarea.delete_line_by_end();
|
||||||
/// assert_eq!(textarea.lines(), ["ab"]);
|
/// assert_eq!(textarea.lines(), ["ab"]);
|
||||||
/// ```
|
/// ```
|
||||||
pub fn delete_line_by_end(&mut self) {
|
pub fn delete_line_by_end(&mut self) -> bool {
|
||||||
self.delete_str(self.cursor.1, usize::MAX);
|
self.delete_str(self.cursor.1, usize::MAX)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Delete string from cursor to head of the line. When the cursor is at head of line, the newline before the cursor
|
/// Delete string from cursor to head of the line. When the cursor is at head of line, the newline before the cursor
|
||||||
/// will be removed.
|
/// will be removed. This method returns if some text was deleted or not in the textarea.
|
||||||
/// ```
|
/// ```
|
||||||
/// use tui_textarea::{TextArea, CursorMove};
|
/// use tui_textarea::{TextArea, CursorMove};
|
||||||
///
|
///
|
||||||
@@ -686,13 +748,16 @@ impl<'a> TextArea<'a> {
|
|||||||
/// textarea.delete_line_by_head();
|
/// textarea.delete_line_by_head();
|
||||||
/// assert_eq!(textarea.lines(), ["cde"]);
|
/// assert_eq!(textarea.lines(), ["cde"]);
|
||||||
/// ```
|
/// ```
|
||||||
pub fn delete_line_by_head(&mut self) {
|
pub fn delete_line_by_head(&mut self) -> bool {
|
||||||
self.delete_str(0, self.cursor.1);
|
self.delete_str(0, self.cursor.1)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Delete a word before cursor. Word boundary appears at spaces, punctuations, and others. For example `fn foo(a)`
|
/// Delete a word before cursor. Word boundary appears at spaces, punctuations, and others. For example `fn foo(a)`
|
||||||
/// consists of words `fn`, `foo`, `(`, `a`, `)`. When the cursor is at head of line, the newline before the cursor
|
/// consists of words `fn`, `foo`, `(`, `a`, `)`. When the cursor is at head of line, the newline before the cursor
|
||||||
/// will be removed.
|
/// will be removed.
|
||||||
|
///
|
||||||
|
/// This method returns if some text was deleted or not in the textarea.
|
||||||
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use tui_textarea::{TextArea, CursorMove};
|
/// use tui_textarea::{TextArea, CursorMove};
|
||||||
///
|
///
|
||||||
@@ -705,20 +770,23 @@ impl<'a> TextArea<'a> {
|
|||||||
/// textarea.delete_word();
|
/// textarea.delete_word();
|
||||||
/// assert_eq!(textarea.lines(), ["aaa "]);
|
/// assert_eq!(textarea.lines(), ["aaa "]);
|
||||||
/// ```
|
/// ```
|
||||||
pub fn delete_word(&mut self) {
|
pub fn delete_word(&mut self) -> bool {
|
||||||
let (r, c) = self.cursor;
|
let (r, c) = self.cursor;
|
||||||
if let Some(col) = find_word_start_backward(&self.lines[r], c) {
|
if let Some(col) = find_word_start_backward(&self.lines[r], c) {
|
||||||
self.delete_str(col, c - col);
|
self.delete_str(col, c - col)
|
||||||
} else if c > 0 {
|
} else if c > 0 {
|
||||||
self.delete_str(0, c);
|
self.delete_str(0, c)
|
||||||
} else {
|
} else {
|
||||||
self.delete_newline();
|
self.delete_newline()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Delete a word next to cursor. Word boundary appears at spaces, punctuations, and others. For example `fn foo(a)`
|
/// Delete a word next to cursor. Word boundary appears at spaces, punctuations, and others. For example `fn foo(a)`
|
||||||
/// consists of words `fn`, `foo`, `(`, `a`, `)`. When the cursor is at end of line, the newline next to the cursor
|
/// consists of words `fn`, `foo`, `(`, `a`, `)`. When the cursor is at end of line, the newline next to the cursor
|
||||||
/// will be removed.
|
/// will be removed.
|
||||||
|
///
|
||||||
|
/// This method returns if some text was deleted or not in the textarea.
|
||||||
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use tui_textarea::TextArea;
|
/// use tui_textarea::TextArea;
|
||||||
///
|
///
|
||||||
@@ -729,24 +797,27 @@ impl<'a> TextArea<'a> {
|
|||||||
/// textarea.delete_next_word();
|
/// textarea.delete_next_word();
|
||||||
/// assert_eq!(textarea.lines(), [" ccc"]);
|
/// assert_eq!(textarea.lines(), [" ccc"]);
|
||||||
/// ```
|
/// ```
|
||||||
pub fn delete_next_word(&mut self) {
|
pub fn delete_next_word(&mut self) -> bool {
|
||||||
let (r, c) = self.cursor;
|
let (r, c) = self.cursor;
|
||||||
let line = &self.lines[r];
|
let line = &self.lines[r];
|
||||||
if let Some(col) = find_word_end_forward(line, c) {
|
if let Some(col) = find_word_end_forward(line, c) {
|
||||||
self.delete_str(c, col - c);
|
self.delete_str(c, col - c)
|
||||||
} else {
|
} else {
|
||||||
let end_col = line.chars().count();
|
let end_col = line.chars().count();
|
||||||
if c < end_col {
|
if c < end_col {
|
||||||
self.delete_str(c, end_col - c);
|
self.delete_str(c, end_col - c)
|
||||||
} else if r + 1 < self.lines.len() {
|
} else if r + 1 < self.lines.len() {
|
||||||
self.cursor = (r + 1, 0);
|
self.cursor = (r + 1, 0);
|
||||||
self.delete_newline();
|
self.delete_newline()
|
||||||
|
} else {
|
||||||
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Paste a string previously deleted by [`TextArea::delete_line_by_head`], [`TextArea::delete_line_by_end`],
|
/// Paste a string previously deleted by [`TextArea::delete_line_by_head`], [`TextArea::delete_line_by_end`],
|
||||||
/// [`TextArea::delete_word`], [`TextArea::delete_next_word`].
|
/// [`TextArea::delete_word`], [`TextArea::delete_next_word`]. This method returns if some text was inserted or not
|
||||||
|
/// in the textarea.
|
||||||
/// ```
|
/// ```
|
||||||
/// use tui_textarea::{TextArea, CursorMove};
|
/// use tui_textarea::{TextArea, CursorMove};
|
||||||
///
|
///
|
||||||
@@ -757,9 +828,9 @@ impl<'a> TextArea<'a> {
|
|||||||
/// textarea.paste();
|
/// textarea.paste();
|
||||||
/// assert_eq!(textarea.lines(), [" bbb cccaaa"]);
|
/// assert_eq!(textarea.lines(), [" bbb cccaaa"]);
|
||||||
/// ```
|
/// ```
|
||||||
pub fn paste(&mut self) {
|
pub fn paste(&mut self) -> bool {
|
||||||
let yank = std::mem::take(&mut self.yank);
|
let yank = std::mem::take(&mut self.yank);
|
||||||
self.insert_str(yank);
|
self.insert_str(yank)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Move the cursor to the position specified by the [`CursorMove`] parameter. For each kind of cursor moves, see
|
/// Move the cursor to the position specified by the [`CursorMove`] parameter. For each kind of cursor moves, see
|
||||||
@@ -780,8 +851,7 @@ impl<'a> TextArea<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Undo the last modification. When no modification is added to the text, this method does not modify contents and
|
/// Undo the last modification. This method returns if the undo modified text contents or not in the textarea.
|
||||||
/// returns `false`.
|
|
||||||
/// ```
|
/// ```
|
||||||
/// use tui_textarea::{TextArea, CursorMove};
|
/// use tui_textarea::{TextArea, CursorMove};
|
||||||
///
|
///
|
||||||
@@ -801,7 +871,7 @@ impl<'a> TextArea<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Redo the last undo change. When no undo change remain, this method does not modify contents and returns `false`.
|
/// Redo the last undo change. This method returns if the redo modified text contents or not in the textarea.
|
||||||
/// ```
|
/// ```
|
||||||
/// use tui_textarea::{TextArea, CursorMove};
|
/// use tui_textarea::{TextArea, CursorMove};
|
||||||
///
|
///
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user