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)]
// 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<String>,
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<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 {
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<B: Backend>(f: &mut Frame<B>, app: &App) {
let vertical_chunks = Layout::default()
.direction(Direction::Vertical)
@ -193,13 +146,16 @@ fn ui<B: Backend>(f: &mut Frame<B>, app: &App) {
f.set_cursor(
vertical_chunks[1].x + app.cmdline.len() as u16 + 1,
vertical_chunks[1].y + 1,
)
);
}
fn ui_output<B: Backend>(f: &mut Frame<B>, 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<B: Backend>(f: &mut Frame<B>, 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],
);