1
0
зеркало из https://github.com/glebtv/tui-textarea.git synced 2026-09-03 14:26:17 +03:00

implement text search in editor example

Этот коммит содержится в:
rhysd
2022-07-08 10:05:53 +09:00
родитель 460c51a796
Коммит 5b08d16214

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

@@ -13,7 +13,7 @@ use tui::backend::CrosstermBackend;
use tui::layout::{Constraint, Direction, Layout};
use tui::style::{Color, Modifier, Style};
use tui::text::{Span, Spans};
use tui::widgets::Paragraph;
use tui::widgets::{Block, Borders, Paragraph};
use tui::Terminal;
use tui_textarea::{Input, Key, TextArea};
@@ -23,6 +23,60 @@ macro_rules! error {
}};
}
struct SearchBox<'a> {
textarea: TextArea<'a>,
open: bool,
}
impl<'a> Default for SearchBox<'a> {
fn default() -> Self {
let mut textarea = TextArea::default();
textarea.set_block(Block::default().borders(Borders::ALL).title("Search"));
Self {
textarea,
open: false,
}
}
}
impl<'a> SearchBox<'a> {
fn open(&mut self) {
self.open = true;
}
fn close(&mut self) {
self.open = false;
let block = self.textarea.block().unwrap().clone();
self.textarea = TextArea::default();
self.textarea.set_block(block);
}
fn height(&self) -> u16 {
if self.open {
3
} else {
0
}
}
fn input(&mut self, input: Input) -> Option<&'_ str> {
match input {
Input {
key: Key::Enter, ..
}
| Input {
key: Key::Char('m'),
ctrl: true,
..
} => None,
input => {
let modified = self.textarea.input(input);
modified.then(|| self.textarea.lines()[0].as_str())
}
}
}
}
struct Buffer<'a> {
textarea: TextArea<'a>,
path: PathBuf,
@@ -73,6 +127,7 @@ struct Editor<'a> {
buffers: Vec<Buffer<'a>>,
term: Terminal<CrosstermBackend<io::Stdout>>,
message: Option<Cow<'static, str>>,
search: SearchBox<'a>,
}
impl<'a> Editor<'a> {
@@ -99,28 +154,36 @@ impl<'a> Editor<'a> {
buffers,
term,
message: None,
search: SearchBox::default(),
})
}
fn run(&mut self) -> io::Result<()> {
let layout = Layout::default()
.direction(Direction::Vertical)
.constraints(
[
Constraint::Min(1),
Constraint::Length(1),
Constraint::Length(1),
]
.as_ref(),
);
loop {
let search_height = self.search.height();
let layout = Layout::default()
.direction(Direction::Vertical)
.constraints(
[
Constraint::Length(search_height),
Constraint::Min(1),
Constraint::Length(1),
Constraint::Length(1),
]
.as_ref(),
);
self.term.draw(|f| {
let chunks = layout.split(f.size());
if search_height > 0 {
f.render_widget(self.search.textarea.widget(), chunks[0]);
}
let buffer = &self.buffers[self.current];
let textarea = &buffer.textarea;
let chunks = layout.split(f.size());
let widget = textarea.widget();
f.render_widget(widget, chunks[0]);
f.render_widget(widget, chunks[1]);
// Render status line
let modified = if buffer.modified { " [modified]" } else { "" };
@@ -138,7 +201,7 @@ impl<'a> Editor<'a> {
]
.as_ref(),
)
.split(chunks[1]);
.split(chunks[2]);
let status_style = Style::default().add_modifier(Modifier::REVERSED);
f.render_widget(Paragraph::new(slot).style(status_style), status_chunks[0]);
f.render_widget(Paragraph::new(path).style(status_style), status_chunks[1]);
@@ -147,6 +210,18 @@ impl<'a> Editor<'a> {
// Render message at bottom
let message = if let Some(message) = self.message.take() {
Spans::from(Span::raw(message))
} else if search_height > 0 {
Spans::from(vec![
Span::raw("Press "),
Span::styled("Enter", Style::default().add_modifier(Modifier::BOLD)),
Span::raw(" to jump to first match and close, "),
Span::styled("Esc", Style::default().add_modifier(Modifier::BOLD)),
Span::raw(" to close, "),
Span::styled("^G or ^→", Style::default().add_modifier(Modifier::BOLD)),
Span::raw(" to search next, "),
Span::styled("M-G or ^←", Style::default().add_modifier(Modifier::BOLD)),
Span::raw(" to search previous"),
])
} else {
Spans::from(vec![
Span::raw("Press "),
@@ -154,38 +229,101 @@ impl<'a> Editor<'a> {
Span::raw(" to quit, "),
Span::styled("^S", Style::default().add_modifier(Modifier::BOLD)),
Span::raw(" to save, "),
Span::styled("^G", Style::default().add_modifier(Modifier::BOLD)),
Span::raw(" to search, "),
Span::styled("^X", Style::default().add_modifier(Modifier::BOLD)),
Span::raw(" to switch buffer"),
])
};
f.render_widget(Paragraph::new(message), chunks[2]);
f.render_widget(Paragraph::new(message), chunks[3]);
})?;
match Input::from(crossterm::event::read()?) {
Input {
key: Key::Char('q'),
ctrl: true,
..
} => break,
Input {
key: Key::Char('x'),
ctrl: true,
..
} => {
self.current = (self.current + 1) % self.buffers.len();
self.message = Some(format!("Switched to buffer #{}", self.current + 1).into());
if search_height > 0 {
let textarea = &mut self.buffers[self.current].textarea;
match Input::from(crossterm::event::read()?) {
Input {
key: Key::Char('g'),
ctrl: true,
alt: false,
}
| Input {
key: Key::Left,
ctrl: true,
..
} => {
if !textarea.search_forward(false) {
self.message = Some("Pattern not found".into());
}
}
Input {
key: Key::Char('g'),
ctrl: false,
alt: true,
}
| Input {
key: Key::Right,
ctrl: true,
..
} => {
if !textarea.search_back(false) {
self.message = Some("Pattern not found".into());
}
}
Input {
key: Key::Enter, ..
} => {
textarea.search_forward(true);
self.search.close();
textarea.set_search_pattern("").unwrap();
}
Input { key: Key::Esc, .. } => {
self.search.close();
textarea.set_search_pattern("").unwrap();
}
input => {
if let Some(query) = self.search.input(input) {
if let Err(err) = textarea.set_search_pattern(query) {
self.message =
Some(format!("Invalid search pattern: {}", err).into());
}
}
}
}
Input {
key: Key::Char('s'),
ctrl: true,
..
} => {
self.buffers[self.current].save()?;
self.message = Some("Saved!".into());
}
input => {
let buffer = &mut self.buffers[self.current];
buffer.modified = buffer.textarea.input(input);
} else {
match Input::from(crossterm::event::read()?) {
Input {
key: Key::Char('q'),
ctrl: true,
..
} => break,
Input {
key: Key::Char('x'),
ctrl: true,
..
} => {
self.current = (self.current + 1) % self.buffers.len();
self.message =
Some(format!("Switched to buffer #{}", self.current + 1).into());
}
Input {
key: Key::Char('s'),
ctrl: true,
..
} => {
self.buffers[self.current].save()?;
self.message = Some("Saved!".into());
}
Input {
key: Key::Char('g'),
ctrl: true,
..
} => {
self.search.open();
}
input => {
let buffer = &mut self.buffers[self.current];
buffer.modified = buffer.textarea.input(input);
}
}
}
}