Implement simple file opener

This commit is contained in:
Patrick Auernig 2023-08-09 15:12:37 +02:00
parent 8fca0f2f6a
commit 2534cbf32c
3 changed files with 125 additions and 28 deletions

11
Cargo.lock generated
View File

@ -708,6 +708,16 @@ dependencies = [
"winapi",
]
[[package]]
name = "tui-input"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3e785f863a3af4c800a2a669d0b64c879b538738e352607e2624d03f868dc01"
dependencies = [
"crossterm 0.27.0",
"unicode-width",
]
[[package]]
name = "unicode-ident"
version = "1.0.11"
@ -824,6 +834,7 @@ dependencies = [
"jrny-save",
"notify",
"ratatui",
"tui-input",
]
[[package]]

View File

@ -24,6 +24,10 @@ optional = true
version = "0.22"
optional = true
[dependencies.tui-input]
version = "0.8"
optional = true
[dependencies.crossterm]
version = "0.27"
optional = true
@ -32,4 +36,4 @@ optional = true
[features]
default = ["watch", "tui"]
watch = ["dep:notify"]
tui = ["dep:ratatui", "dep:crossterm"]
tui = ["dep:ratatui", "dep:tui-input", "dep:crossterm"]

View File

@ -8,7 +8,7 @@ use std::time::Duration;
use anyhow::Result;
use clap::Parser as ArgParser;
use crossterm::event::{Event, KeyCode, KeyEvent};
use crossterm::event::{DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyEvent};
use crossterm::terminal::{
disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen,
};
@ -17,6 +17,8 @@ use jrny_save::{Savefile, LEVEL_NAMES};
use ratatui::backend::CrosstermBackend;
use ratatui::layout::{Alignment, Constraint, Direction, Layout, Rect};
use ratatui::widgets::{Block, Borders, Cell, Padding, Paragraph, Row, Table};
use tui_input::backend::crossterm::EventHandler;
use tui_input::Input;
#[cfg(feature = "watch")]
use crate::watcher::FileWatcher;
@ -32,6 +34,8 @@ pub struct Args {
struct State {
current_path: PathBuf,
current_file: Savefile,
mode: Mode,
file_select: Input,
#[cfg(feature = "watch")]
file_watcher: Option<FileWatcher>,
}
@ -42,6 +46,12 @@ struct State {
enum Message {
Exit,
ToggleFileSelect,
SetMode(Mode),
LoadFile,
#[cfg(feature = "watch")]
ToggleFileWatch,
@ -50,6 +60,15 @@ enum Message {
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
enum Mode {
#[default]
Normal,
SelectFile,
}
type Terminal = ratatui::Terminal<CrosstermBackend<Stdout>>;
type Frame<'a> = ratatui::Frame<'a, CrosstermBackend<Stdout>>;
@ -63,6 +82,8 @@ pub(crate) fn execute(_app_args: &AppArgs, args: &Args) -> Result<()> {
let state = State {
current_path: args.path.clone(),
current_file: savefile,
mode: Mode::default(),
file_select: Input::default(),
#[cfg(feature = "watch")]
file_watcher: None,
};
@ -84,7 +105,7 @@ fn run(terminal: &mut Terminal, mut state: State) -> Result<()> {
render(&state, frame);
})?;
handle_events(&mut evq_tx)?;
handle_events(&mut evq_tx, &mut state)?;
let message = match evq_rx.try_recv() {
Ok(msg) => msg,
@ -95,6 +116,29 @@ fn run(terminal: &mut Terminal, mut state: State) -> Result<()> {
match message {
Message::Exit => break,
Message::SetMode(mode) => {
state.mode = mode;
}
Message::LoadFile => {
let path = PathBuf::from(state.file_select.value());
state.current_file = load_savefile(&path)?;
state.current_path = path;
#[cfg(feature = "watch")]
if state.file_watcher.is_some() {
state.file_watcher = None;
}
}
Message::ToggleFileSelect => {
state.mode = match state.mode {
Mode::SelectFile => Mode::Normal,
_ => Mode::SelectFile,
};
}
#[cfg(feature = "watch")]
Message::ToggleFileWatch => {
if state.file_watcher.is_none() {
@ -121,13 +165,13 @@ fn run(terminal: &mut Terminal, mut state: State) -> Result<()> {
}
fn handle_events(event_queue: &mut mpsc::Sender<Message>) -> Result<()> {
fn handle_events(event_queue: &mut mpsc::Sender<Message>, state: &mut State) -> Result<()> {
if !event::poll(Duration::from_millis(250))? {
return Ok(());
}
match event::read()? {
Event::Key(key) => handle_keyboard_input(key, event_queue)?,
Event::Key(key) => handle_keyboard_input(key, event_queue, state)?,
_ => return Ok(()),
}
@ -135,18 +179,41 @@ fn handle_events(event_queue: &mut mpsc::Sender<Message>) -> Result<()> {
}
fn handle_keyboard_input(key: KeyEvent, event_queue: &mut mpsc::Sender<Message>) -> Result<()> {
let message = match key.code {
KeyCode::Char('q') => Message::Exit,
fn handle_keyboard_input(
key: KeyEvent,
event_queue: &mut mpsc::Sender<Message>,
state: &mut State,
) -> Result<()> {
match (state.mode, key.code) {
(_, KeyCode::Esc) => {
event_queue.send(Message::SetMode(Mode::Normal))?;
}
(Mode::SelectFile, KeyCode::Enter) => {
event_queue.send(Message::LoadFile)?;
event_queue.send(Message::ToggleFileSelect)?;
}
(Mode::SelectFile, _) => {
state.file_select.handle_event(&Event::Key(key));
}
(Mode::Normal, KeyCode::Char('q')) => {
event_queue.send(Message::Exit)?;
}
(Mode::Normal, KeyCode::Char('o')) => {
event_queue.send(Message::ToggleFileSelect)?;
}
#[cfg(feature = "watch")]
KeyCode::Char('w') => Message::ToggleFileWatch,
(Mode::Normal, KeyCode::Char('w')) => {
event_queue.send(Message::ToggleFileWatch)?;
}
_ => return Ok(()),
_ => (),
};
event_queue.send(message)?;
Ok(())
}
@ -156,7 +223,7 @@ fn setup() -> Result<Terminal> {
enable_raw_mode()?;
execute!(stdout, EnterAlternateScreen)?;
execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?;
Ok(Terminal::new(CrosstermBackend::new(stdout))?)
}
@ -165,7 +232,11 @@ fn setup() -> Result<Terminal> {
fn reset(mut terminal: Terminal) -> Result<()> {
disable_raw_mode()?;
execute!(terminal.backend_mut(), LeaveAlternateScreen)?;
execute!(
terminal.backend_mut(),
LeaveAlternateScreen,
DisableMouseCapture
)?;
terminal.show_cursor()?;
@ -194,21 +265,32 @@ fn render(state: &State, mut frame: &mut Frame) {
let status_block = Block::default().padding(Padding::horizontal(2));
match state.mode {
#[cfg(feature = "watch")]
let text = format!(
"{} file: {}",
if state.file_watcher.is_some() {
"Watching"
} else {
"Showing"
},
state.current_path.display()
);
#[cfg(not(feature = "watch"))]
let text = format!("Showing file: {}", state.current_path.display());
Mode::Normal if state.file_watcher.is_some() => {
let text = format!("Watching file: {}", state.current_path.display());
let status = Paragraph::new(text).block(status_block);
frame.render_widget(status, rows[1])
frame.render_widget(status, rows[1]);
}
Mode::Normal => {
let text = format!("Showing file: {}", state.current_path.display());
let status = Paragraph::new(text).block(status_block);
frame.render_widget(status, rows[1]);
}
Mode::SelectFile => {
let scroll = state.file_select.visual_scroll(rows[1].width as usize);
let input = Paragraph::new(state.file_select.value())
.scroll((0, scroll as u16))
.block(status_block);
frame.render_widget(input, rows[1]);
frame.set_cursor(
rows[1].x + (state.file_select.visual_cursor() as u16) + 2,
rows[1].y,
);
}
}
}