1
0
зеркало из https://github.com/glebtv/tui-textarea.git synced 2026-09-03 22:35:50 +03:00
Этот коммит содержится в:
rhysd
2022-06-09 22:39:03 +09:00
родитель d3a7806df2
Коммит fc9f017db3
5 изменённых файлов: 186 добавлений и 10 удалений

Просмотреть файл

@@ -18,7 +18,7 @@ fn main() -> io::Result<()> {
let mut term = Terminal::new(backend)?;
let mut textarea = TextArea::default();
textarea.block(Block::default().borders(Borders::ALL).title("EXAMPLE"));
textarea.set_block(Block::default().borders(Borders::ALL).title("EXAMPLE"));
let layout = Layout::default()
.direction(Direction::Vertical)
.constraints([Constraint::Min(1)].as_ref());

93
src/edit.rs Обычный файл
Просмотреть файл

@@ -0,0 +1,93 @@
pub enum EditKind {
InsertChar(char, usize),
DeleteChar(char, usize),
InsertNewline(usize),
DeleteNewline(usize),
Insert(String, usize),
}
pub struct Edit {
kind: EditKind,
cursor_before: (usize, usize),
cursor_after: (usize, usize),
}
impl Edit {
pub fn new(
kind: EditKind,
cursor_before: (usize, usize),
cursor_after: (usize, usize),
) -> Self {
Self {
kind,
cursor_before,
cursor_after,
}
}
pub fn redo(&self, lines: &mut Vec<String>) {
let (row, _) = self.cursor_before;
match self.kind {
EditKind::InsertChar(c, i) => {
lines[row].insert(i, c);
}
EditKind::DeleteChar(_, i) => {
lines[row].remove(i);
}
EditKind::InsertNewline(i) => {
let line = &mut lines[row];
let next_line = line[i..].to_string();
line.truncate(i);
line.push(' ');
lines.insert(row + 1, next_line);
}
EditKind::DeleteNewline(_) => {
if row > 0 {
let line = lines.remove(row);
let prev_line = &mut lines[row - 1];
prev_line.pop(); // Remove trailing space
prev_line.push_str(&line);
}
}
EditKind::Insert(ref s, i) => {
lines[row].insert_str(i, s.as_str());
}
}
}
pub fn undo(&self, lines: &mut Vec<String>) {
let (row, _) = self.cursor_after;
match self.kind {
EditKind::InsertChar(_, i) => {
lines[row].remove(i);
}
EditKind::DeleteChar(c, i) => {
lines[row].insert(i, c);
}
EditKind::InsertNewline(_) => {
let line = lines.remove(row);
let prev_line = &mut lines[row - 1];
prev_line.pop(); // Remove trailing space
prev_line.push_str(&line);
}
EditKind::DeleteNewline(i) => {
let line = &mut lines[row];
let next_line = line[i..].to_string();
line.truncate(i);
line.push(' ');
lines.insert(row + 1, next_line);
}
EditKind::Insert(ref s, i) => {
lines[row].replace_range(i..s.len(), "");
}
}
}
pub fn cursor_before(&self) -> (usize, usize) {
self.cursor_before
}
pub fn cursor_after(&self) -> (usize, usize) {
self.cursor_after
}
}

52
src/history.rs Обычный файл
Просмотреть файл

@@ -0,0 +1,52 @@
use crate::edit::Edit;
use std::collections::VecDeque;
pub struct EditHistory {
index: usize,
max_items: usize,
edits: VecDeque<Edit>,
}
impl EditHistory {
pub fn new(max_items: usize) -> Self {
Self {
index: 0,
max_items,
edits: VecDeque::new(),
}
}
pub fn push(&mut self, edit: Edit) {
if self.edits.len() == self.max_items {
self.edits.pop_front();
self.index -= 1;
}
if self.index < self.edits.len() {
self.edits.truncate(self.index);
}
self.index += 1;
self.edits.push_back(edit);
}
pub fn redo(&mut self, lines: &mut Vec<String>) -> Option<(usize, usize)> {
if self.index == self.edits.len() {
return None;
}
let edit = &self.edits[self.index];
edit.redo(lines);
self.index += 1;
Some(edit.cursor_after())
}
pub fn undo(&mut self, lines: &mut Vec<String>) -> Option<(usize, usize)> {
if self.index == 0 {
return None;
}
self.index -= 1;
let edit = &self.edits[self.index];
edit.undo(lines);
Some(edit.cursor_before())
}
}

Просмотреть файл

@@ -1,3 +1,5 @@
mod edit;
mod history;
mod input;
mod textarea;

Просмотреть файл

@@ -1,3 +1,5 @@
use crate::edit::{Edit, EditKind};
use crate::history::EditHistory;
use crate::input::{Input, Key};
use tui::style::{Modifier, Style};
use tui::text::{Span, Spans, Text};
@@ -9,6 +11,7 @@ pub struct TextArea<'a> {
style: Style,
cursor: (usize, usize), // 0-base
tab: &'a str,
history: EditHistory,
}
impl<'a> Default for TextArea<'a> {
@@ -19,6 +22,7 @@ impl<'a> Default for TextArea<'a> {
style: Style::default(),
cursor: (0, 0),
tab: " ",
history: EditHistory::new(50),
}
}
}
@@ -36,6 +40,8 @@ impl<'a> TextArea<'a> {
Key::Char('b') => self.cursor_back(),
Key::Char('a') => self.cursor_start(),
Key::Char('e') => self.cursor_end(),
Key::Char('u') => self.undo(),
Key::Char('r') => self.redo(),
_ => {}
}
} else {
@@ -83,12 +89,18 @@ impl<'a> TextArea<'a> {
);
}
fn push_history(&mut self, kind: EditKind, cursor_before: (usize, usize)) {
let edit = Edit::new(kind, cursor_before, self.cursor);
self.history.push(edit);
}
pub fn insert_char(&mut self, c: char) {
let (row, col) = self.cursor;
let line = &mut self.lines[row];
if let Some((i, _)) = line.char_indices().nth(col) {
line.insert(i, c);
self.cursor.1 += 1;
self.push_history(EditKind::InsertChar(c, i), (row, col));
}
}
@@ -103,6 +115,7 @@ impl<'a> TextArea<'a> {
if let Some((i, _)) = line.char_indices().nth(col) {
line.insert_str(i, s);
self.cursor.1 += s.chars().count();
self.push_history(EditKind::Insert(s.to_string(), i), (row, col));
}
}
@@ -126,6 +139,7 @@ impl<'a> TextArea<'a> {
line.push(' ');
self.lines.insert(row + 1, next_line);
self.cursor = (row + 1, 0);
self.push_history(EditKind::InsertNewline(idx), (row, col));
}
pub fn delete_char(&mut self) {
@@ -135,16 +149,19 @@ impl<'a> TextArea<'a> {
let line = self.lines.remove(row);
let prev_line = &mut self.lines[row - 1];
prev_line.pop(); // Remove trailing space
let prev_line_end = prev_line.len();
prev_line.push_str(&line);
self.cursor = (row - 1, prev_line.chars().count() - 1);
self.push_history(EditKind::DeleteNewline(prev_line_end), (row, col));
}
return;
}
let line = &mut self.lines[row];
if let Some((i, _)) = line.char_indices().nth(col - 1) {
if let Some((i, c)) = line.char_indices().nth(col - 1) {
line.remove(i);
self.cursor.1 -= 1;
self.push_history(EditKind::DeleteChar(c, i), (row, col));
}
}
@@ -202,6 +219,18 @@ impl<'a> TextArea<'a> {
self.cursor.1 = self.lines[self.cursor.0].chars().count() - 1;
}
pub fn undo(&mut self) {
if let Some(cursor) = self.history.undo(&mut self.lines) {
self.cursor = cursor;
}
}
pub fn redo(&mut self) {
if let Some(cursor) = self.history.redo(&mut self.lines) {
self.cursor = cursor;
}
}
pub fn widget(&'a self) -> impl Widget + 'a {
let mut lines = Vec::with_capacity(self.lines.len());
for (i, l) in self.lines.iter().enumerate() {
@@ -227,29 +256,29 @@ impl<'a> TextArea<'a> {
p
}
pub fn style(&mut self, style: Style) -> &mut Self {
pub fn set_style(&mut self, style: Style) {
self.style = style;
self
}
pub fn block(&mut self, block: Block<'a>) -> &mut Self {
pub fn set_block(&mut self, block: Block<'a>) {
self.block = Some(block);
self
}
pub fn remove_block(&mut self) -> &mut Self {
pub fn remove_block(&mut self) {
self.block = None;
self
}
pub fn tab(&mut self, tab: &'a str) -> &mut Self {
pub fn set_tab(&mut self, tab: &'a str) {
assert!(
tab.chars().all(|c| c == ' '),
"tab string must consist of spaces but got {:?}",
tab,
);
self.tab = tab;
self
}
pub fn set_max_histories(&mut self, max: usize) {
self.history = EditHistory::new(max);
}
pub fn lines(&'a self) -> impl Iterator<Item = &'a str> {