1
0
зеркало из https://github.com/glebtv/tui-textarea.git synced 2026-09-08 00:15:50 +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::layout::{Constraint, Direction, Layout};
use tui::style::{Color, Modifier, Style}; use tui::style::{Color, Modifier, Style};
use tui::text::{Span, Spans}; use tui::text::{Span, Spans};
use tui::widgets::Paragraph; use tui::widgets::{Block, Borders, Paragraph};
use tui::Terminal; use tui::Terminal;
use tui_textarea::{Input, Key, TextArea}; 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> { struct Buffer<'a> {
textarea: TextArea<'a>, textarea: TextArea<'a>,
path: PathBuf, path: PathBuf,
@@ -73,6 +127,7 @@ struct Editor<'a> {
buffers: Vec<Buffer<'a>>, buffers: Vec<Buffer<'a>>,
term: Terminal<CrosstermBackend<io::Stdout>>, term: Terminal<CrosstermBackend<io::Stdout>>,
message: Option<Cow<'static, str>>, message: Option<Cow<'static, str>>,
search: SearchBox<'a>,
} }
impl<'a> Editor<'a> { impl<'a> Editor<'a> {
@@ -99,28 +154,36 @@ impl<'a> Editor<'a> {
buffers, buffers,
term, term,
message: None, message: None,
search: SearchBox::default(),
}) })
} }
fn run(&mut self) -> io::Result<()> { 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 { 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| { 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 buffer = &self.buffers[self.current];
let textarea = &buffer.textarea; let textarea = &buffer.textarea;
let chunks = layout.split(f.size());
let widget = textarea.widget(); let widget = textarea.widget();
f.render_widget(widget, chunks[0]); f.render_widget(widget, chunks[1]);
// Render status line // Render status line
let modified = if buffer.modified { " [modified]" } else { "" }; let modified = if buffer.modified { " [modified]" } else { "" };
@@ -138,7 +201,7 @@ impl<'a> Editor<'a> {
] ]
.as_ref(), .as_ref(),
) )
.split(chunks[1]); .split(chunks[2]);
let status_style = Style::default().add_modifier(Modifier::REVERSED); 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(slot).style(status_style), status_chunks[0]);
f.render_widget(Paragraph::new(path).style(status_style), status_chunks[1]); f.render_widget(Paragraph::new(path).style(status_style), status_chunks[1]);
@@ -147,6 +210,18 @@ impl<'a> Editor<'a> {
// Render message at bottom // Render message at bottom
let message = if let Some(message) = self.message.take() { let message = if let Some(message) = self.message.take() {
Spans::from(Span::raw(message)) 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 { } else {
Spans::from(vec![ Spans::from(vec![
Span::raw("Press "), Span::raw("Press "),
@@ -154,38 +229,101 @@ impl<'a> Editor<'a> {
Span::raw(" to quit, "), Span::raw(" to quit, "),
Span::styled("^S", Style::default().add_modifier(Modifier::BOLD)), Span::styled("^S", Style::default().add_modifier(Modifier::BOLD)),
Span::raw(" to save, "), 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::styled("^X", Style::default().add_modifier(Modifier::BOLD)),
Span::raw(" to switch buffer"), 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()?) { if search_height > 0 {
Input { let textarea = &mut self.buffers[self.current].textarea;
key: Key::Char('q'), match Input::from(crossterm::event::read()?) {
ctrl: true, Input {
.. key: Key::Char('g'),
} => break, ctrl: true,
Input { alt: false,
key: Key::Char('x'), }
ctrl: true, | Input {
.. key: Key::Left,
} => { ctrl: true,
self.current = (self.current + 1) % self.buffers.len(); ..
self.message = Some(format!("Switched to buffer #{}", self.current + 1).into()); } => {
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 { } else {
key: Key::Char('s'), match Input::from(crossterm::event::read()?) {
ctrl: true, Input {
.. key: Key::Char('q'),
} => { ctrl: true,
self.buffers[self.current].save()?; ..
self.message = Some("Saved!".into()); } => break,
} Input {
input => { key: Key::Char('x'),
let buffer = &mut self.buffers[self.current]; ctrl: true,
buffer.modified = buffer.textarea.input(input); ..
} => {
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);
}
} }
} }
} }