diff --git a/src/command.rs b/src/command.rs new file mode 100644 index 0000000..bdb53dd --- /dev/null +++ b/src/command.rs @@ -0,0 +1,55 @@ +use std::{ + io::Write, + process::{Command, Stdio}, +}; + +use tui::widgets::Paragraph; + +use crate::App; + +pub struct CommandResult<'a> { + pub status_success: bool, + pub stdout: Paragraph<'a>, + pub stderr: Paragraph<'a>, +} + +impl Default for CommandResult<'_> { + fn default() -> Self { + Self { + status_success: true, + stdout: Paragraph::new(""), + stderr: Paragraph::new(""), + } + } +} + +pub fn run(app: &mut App) { + let mut child = Command::new("jq") + .arg("-C") + .arg(&app.cmdline) + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .expect("Could not spawn child process"); + + let mut stdin = child.stdin.take().expect("Could not take stdin"); + let text_orig_clone = app.text_orig.clone(); + std::thread::spawn(move || { + stdin + .write_all(text_orig_clone.as_bytes()) + .expect("Failed to write to stdin"); + }); + + // Collect the output + let output = child.wait_with_output().expect("Failed to read stdout"); + + let stderr_string = ansi4tui::bytes_to_text(output.stderr); + let stdout_string = ansi4tui::bytes_to_text(output.stdout); + + app.command_result = CommandResult { + status_success: output.status.success(), + stdout: Paragraph::new(stdout_string), + stderr: Paragraph::new(stderr_string), + } +} diff --git a/src/main.rs b/src/main.rs index b798b3a..9c4cade 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,9 +1,9 @@ -// Easy mode #![allow(dead_code, unused_imports)] -// Hard mode #![warn(clippy::all, clippy::pedantic, clippy::nursery)] -// #![allow(clippy::cast_possible_truncation, clippy::multiple_crate_versions)] +#![allow(clippy::module_name_repetitions, clippy::cast_possible_truncation)] +mod command; +use command::CommandResult; use std::{ io::{self, Write}, process::{Command, Stdio}, @@ -11,6 +11,7 @@ use std::{ thread, time::Duration, }; +use tui::text::Text; use tui::{ backend::{Backend, CrosstermBackend}, layout::{Constraint, Direction, Layout, Rect}, @@ -24,47 +25,28 @@ use crossterm::{ terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, }; -struct CommandResult { - pub status_success: bool, - pub stdout: String, - pub stderr: String, -} - -impl Default for CommandResult { - fn default() -> Self { - Self { - status_success: true, - stdout: String::default(), - stderr: String::default(), - } - } -} - /// The state of the application -struct App { +pub struct App<'a> { /// The line the user inputs cmdline: String, /// Original text text_orig: Arc, - command_result: CommandResult, - - /// Transformed text - // text_transformed: String, - // text_transformed_stderr: String, + /// The result of a command execution + command_result: CommandResult<'a>, /// Should every keystroke transform the original text? autorun: bool, } -impl App { +impl App<'_> { + #[must_use] pub fn new_with_input(input: String) -> Self { Self { - cmdline: String::from(""), + cmdline: String::new(), text_orig: Arc::new(input), command_result: CommandResult::default(), - // text_transformed: String::from(""), autorun: true, } } @@ -104,65 +86,36 @@ fn main() -> Result<(), io::Error> { Ok(()) } -fn run_app(terminal: &mut Terminal, mut app: App) -> io::Result<()> { +fn run_app(terminal: &mut Terminal, mut app: App) -> io::Result> { loop { terminal.draw(|f| ui(f, &app))?; if let Event::Key(key) = event::read()? { match key.code { KeyCode::Esc => { - return Ok(()); + return Ok(None); } KeyCode::Char(c) => { app.cmdline.push(c); if app.autorun { - run_command(&mut app); + command::run(&mut app); } } KeyCode::Backspace => { app.cmdline.pop(); if app.autorun { - run_command(&mut app); + command::run(&mut app); } } - KeyCode::Enter => {} + KeyCode::Enter => { + return Ok(Some(app.cmdline.clone())); + } _ => {} } } } } -fn run_command(app: &mut App) { - let mut child = Command::new("jq") - .arg("-C") - .arg(&app.cmdline) - .stdin(Stdio::piped()) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .spawn() - .expect("Could not spawn child process"); - - let mut stdin = child.stdin.take().expect("Could not take stdin"); - let text_orig_clone = app.text_orig.clone(); - std::thread::spawn(move || { - stdin - .write_all(text_orig_clone.as_bytes()) - .expect("Failed to write to stdin") - }); - - // Collect the output - let output = child.wait_with_output().expect("Failed to read stdout"); - - let stderr_string = String::from_utf8_lossy(&output.stderr).to_string(); - let stdout_string = String::from_utf8_lossy(&output.stdout).to_string(); - - app.command_result = CommandResult { - status_success: output.status.success(), - stdout: stdout_string, - stderr: stderr_string, - } -} - fn ui(f: &mut Frame, app: &App) { let vertical_chunks = Layout::default() .direction(Direction::Vertical) @@ -193,13 +146,16 @@ fn ui(f: &mut Frame, app: &App) { f.set_cursor( vertical_chunks[1].x + app.cmdline.len() as u16 + 1, vertical_chunks[1].y + 1, - ) + ); } fn ui_output(f: &mut Frame, output_region: Rect, app: &App) { if app.command_result.status_success { f.render_widget( - Paragraph::new(app.command_result.stdout.as_ref()) + // TODO: Do we really have to clone here? + app.command_result + .stdout + .clone() .block(Block::default().title("New").borders(Borders::ALL)), output_region, ); @@ -210,12 +166,18 @@ fn ui_output(f: &mut Frame, output_region: Rect, app: &App) { .split(output_region); f.render_widget( - Paragraph::new(app.command_result.stdout.as_ref()) + // TODO: Do we really have to clone here? + app.command_result + .stdout + .clone() .block(Block::default().title("Output").borders(Borders::ALL)), chunks[0], ); f.render_widget( - Paragraph::new(app.command_result.stderr.as_ref()) + // TODO: Do we really have to clone here? + app.command_result + .stderr + .clone() .block(Block::default().title("Error").borders(Borders::ALL)), chunks[1], );