This commit is contained in:
Austen Adler 2022-12-15 23:19:17 -05:00
parent 3a0c42175b
commit a8672a3197
2 changed files with 85 additions and 68 deletions

55
src/command.rs Normal file
View File

@ -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),
}
}

View File

@ -1,9 +1,9 @@
// Easy mode
#![allow(dead_code, unused_imports)] #![allow(dead_code, unused_imports)]
// Hard mode
#![warn(clippy::all, clippy::pedantic, clippy::nursery)] #![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::{ use std::{
io::{self, Write}, io::{self, Write},
process::{Command, Stdio}, process::{Command, Stdio},
@ -11,6 +11,7 @@ use std::{
thread, thread,
time::Duration, time::Duration,
}; };
use tui::text::Text;
use tui::{ use tui::{
backend::{Backend, CrosstermBackend}, backend::{Backend, CrosstermBackend},
layout::{Constraint, Direction, Layout, Rect}, layout::{Constraint, Direction, Layout, Rect},
@ -24,47 +25,28 @@ use crossterm::{
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, 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 /// The state of the application
struct App { pub struct App<'a> {
/// The line the user inputs /// The line the user inputs
cmdline: String, cmdline: String,
/// Original text /// Original text
text_orig: Arc<String>, text_orig: Arc<String>,
command_result: CommandResult, /// The result of a command execution
command_result: CommandResult<'a>,
/// Transformed text
// text_transformed: String,
// text_transformed_stderr: String,
/// Should every keystroke transform the original text? /// Should every keystroke transform the original text?
autorun: bool, autorun: bool,
} }
impl App { impl App<'_> {
#[must_use]
pub fn new_with_input(input: String) -> Self { pub fn new_with_input(input: String) -> Self {
Self { Self {
cmdline: String::from(""), cmdline: String::new(),
text_orig: Arc::new(input), text_orig: Arc::new(input),
command_result: CommandResult::default(), command_result: CommandResult::default(),
// text_transformed: String::from(""),
autorun: true, autorun: true,
} }
} }
@ -104,65 +86,36 @@ fn main() -> Result<(), io::Error> {
Ok(()) Ok(())
} }
fn run_app<B: Backend>(terminal: &mut Terminal<B>, mut app: App) -> io::Result<()> { fn run_app<B: Backend>(terminal: &mut Terminal<B>, mut app: App) -> io::Result<Option<String>> {
loop { loop {
terminal.draw(|f| ui(f, &app))?; terminal.draw(|f| ui(f, &app))?;
if let Event::Key(key) = event::read()? { if let Event::Key(key) = event::read()? {
match key.code { match key.code {
KeyCode::Esc => { KeyCode::Esc => {
return Ok(()); return Ok(None);
} }
KeyCode::Char(c) => { KeyCode::Char(c) => {
app.cmdline.push(c); app.cmdline.push(c);
if app.autorun { if app.autorun {
run_command(&mut app); command::run(&mut app);
} }
} }
KeyCode::Backspace => { KeyCode::Backspace => {
app.cmdline.pop(); app.cmdline.pop();
if app.autorun { 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<B: Backend>(f: &mut Frame<B>, app: &App) { fn ui<B: Backend>(f: &mut Frame<B>, app: &App) {
let vertical_chunks = Layout::default() let vertical_chunks = Layout::default()
.direction(Direction::Vertical) .direction(Direction::Vertical)
@ -193,13 +146,16 @@ fn ui<B: Backend>(f: &mut Frame<B>, app: &App) {
f.set_cursor( f.set_cursor(
vertical_chunks[1].x + app.cmdline.len() as u16 + 1, vertical_chunks[1].x + app.cmdline.len() as u16 + 1,
vertical_chunks[1].y + 1, vertical_chunks[1].y + 1,
) );
} }
fn ui_output<B: Backend>(f: &mut Frame<B>, output_region: Rect, app: &App) { fn ui_output<B: Backend>(f: &mut Frame<B>, output_region: Rect, app: &App) {
if app.command_result.status_success { if app.command_result.status_success {
f.render_widget( 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)), .block(Block::default().title("New").borders(Borders::ALL)),
output_region, output_region,
); );
@ -210,12 +166,18 @@ fn ui_output<B: Backend>(f: &mut Frame<B>, output_region: Rect, app: &App) {
.split(output_region); .split(output_region);
f.render_widget( 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)), .block(Block::default().title("Output").borders(Borders::ALL)),
chunks[0], chunks[0],
); );
f.render_widget( 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)), .block(Block::default().title("Error").borders(Borders::ALL)),
chunks[1], chunks[1],
); );