Use ropey
This commit is contained in:
parent
853770613e
commit
d12e3bb43a
129
Cargo.lock
generated
129
Cargo.lock
generated
@ -298,7 +298,10 @@ dependencies = [
|
||||
"crossbeam",
|
||||
"crossterm",
|
||||
"parking_lot",
|
||||
"ropey",
|
||||
"shellwords",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
"tui",
|
||||
]
|
||||
|
||||
@ -364,6 +367,16 @@ dependencies = [
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nu-ansi-term"
|
||||
version = "0.46.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84"
|
||||
dependencies = [
|
||||
"overload",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-derive"
|
||||
version = "0.3.3"
|
||||
@ -384,6 +397,12 @@ dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860"
|
||||
|
||||
[[package]]
|
||||
name = "opaque-debug"
|
||||
version = "0.3.0"
|
||||
@ -399,6 +418,12 @@ dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "overload"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.12.1"
|
||||
@ -470,6 +495,12 @@ dependencies = [
|
||||
"siphasher",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116"
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.17"
|
||||
@ -582,6 +613,16 @@ version = "0.6.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848"
|
||||
|
||||
[[package]]
|
||||
name = "ropey"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbd22239fafefc42138ca5da064f3c17726a80d2379d817a3521240e78dd0064"
|
||||
dependencies = [
|
||||
"smallvec",
|
||||
"str_indices",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
version = "1.1.0"
|
||||
@ -619,6 +660,15 @@ dependencies = [
|
||||
"opaque-debug",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sharded-slab"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shellwords"
|
||||
version = "1.1.0"
|
||||
@ -681,6 +731,12 @@ version = "1.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
|
||||
|
||||
[[package]]
|
||||
name = "str_indices"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f026164926842ec52deb1938fae44f83dfdb82d0a5b0270c5bd5935ab74d6dd"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.105"
|
||||
@ -766,6 +822,73 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thread_local"
|
||||
version = "1.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing"
|
||||
version = "0.1.37"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"pin-project-lite",
|
||||
"tracing-attributes",
|
||||
"tracing-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-attributes"
|
||||
version = "0.1.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-core"
|
||||
version = "0.1.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"valuable",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-log"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"log",
|
||||
"tracing-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-subscriber"
|
||||
version = "0.3.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a6176eae26dd70d0c919749377897b54a9276bd7061339665dd68777926b5a70"
|
||||
dependencies = [
|
||||
"nu-ansi-term",
|
||||
"sharded-slab",
|
||||
"smallvec",
|
||||
"thread_local",
|
||||
"tracing-core",
|
||||
"tracing-log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tui"
|
||||
version = "0.19.0"
|
||||
@ -815,6 +938,12 @@ version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "936e4b492acfd135421d8dca4b1aa80a7bfc26e702ef3af710e0752684df5372"
|
||||
|
||||
[[package]]
|
||||
name = "valuable"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.4"
|
||||
|
@ -14,3 +14,6 @@ anyhow = "1.0.66"
|
||||
shellwords = "1.1.0"
|
||||
crossbeam = "0.8.2"
|
||||
parking_lot = "0.12.1"
|
||||
tracing = { version = "0.1.37", features = ["release_max_level_off"] }
|
||||
tracing-subscriber = "0.3.16"
|
||||
ropey = "1.5.0"
|
||||
|
@ -1,16 +1,20 @@
|
||||
use crate::event::EventMessage;
|
||||
use crate::CommandOptions;
|
||||
use ansi4tui::bytes_to_text;
|
||||
use anyhow::bail;
|
||||
use anyhow::Context;
|
||||
use anyhow::Result;
|
||||
use crossbeam::channel::Receiver;
|
||||
use crossbeam::channel::Sender;
|
||||
use parking_lot::RwLock;
|
||||
use ropey::Rope;
|
||||
use std::collections::hash_map::DefaultHasher;
|
||||
use std::sync::Arc;
|
||||
use std::{
|
||||
io::Write,
|
||||
process::{Command, Stdio},
|
||||
};
|
||||
use tui::widgets::Paragraph;
|
||||
|
||||
pub type CommandRequest = (Arc<RwLock<CommandOptions>>, Arc<String>);
|
||||
use crate::App;
|
||||
@ -18,22 +22,24 @@ use crate::App;
|
||||
#[derive(Debug)]
|
||||
pub enum CommandCompleted {
|
||||
Success(CommandResult),
|
||||
Failure(Vec<u8>),
|
||||
Failure(Rope),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct CommandResult {
|
||||
pub status_success: bool,
|
||||
pub stdout: Vec<u8>,
|
||||
pub stderr: Vec<u8>,
|
||||
pub stdout: Rope,
|
||||
pub stderr: Rope,
|
||||
}
|
||||
|
||||
// TODO: Result?
|
||||
impl Default for CommandResult {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
status_success: true,
|
||||
stdout: vec![],
|
||||
stderr: vec![],
|
||||
// TODO: Option<Rope>
|
||||
stdout: Rope::from_str(""),
|
||||
stderr: Rope::from_str(""),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -49,13 +55,14 @@ pub fn command_event_loop(
|
||||
match run_inner(command_request) {
|
||||
Ok(c) => {
|
||||
// If there was no stdout and the command failed, don't touch stdout
|
||||
if !c.status_success && c.stdout.is_empty() {
|
||||
if !c.status_success && c.stdout.len_bytes() == 0 {
|
||||
CommandCompleted::Failure(c.stderr)
|
||||
} else {
|
||||
// CommandCompleted::Success((c, FormattedCommandResult::from(&c)))
|
||||
CommandCompleted::Success(c)
|
||||
}
|
||||
}
|
||||
Err(e) => CommandCompleted::Failure(e.to_string().as_bytes().to_vec()),
|
||||
Err(e) => CommandCompleted::Failure(Rope::from_str(&e.to_string())),
|
||||
},
|
||||
))?;
|
||||
}
|
||||
@ -67,24 +74,6 @@ pub fn command_event_loop(
|
||||
}
|
||||
}
|
||||
|
||||
// pub fn run(app: Arc<App>) {
|
||||
// match run_inner(app) {
|
||||
// Ok(c) => {
|
||||
// // If there was no stdout and the command failed, don't touch stdout
|
||||
// if !c.status_success && c.stdout.is_empty() {
|
||||
// app.command_result.stderr = c.stderr;
|
||||
// app.command_result.status_success = c.status_success;
|
||||
// } else {
|
||||
// app.command_result = c;
|
||||
// }
|
||||
// }
|
||||
// Err(e) => {
|
||||
// app.command_result.status_success = false;
|
||||
// app.command_result.stderr = e.to_string().as_bytes().to_vec();
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
fn run_inner(command_request: CommandRequest) -> Result<CommandResult> {
|
||||
// Spawn the child
|
||||
let mut child = {
|
||||
@ -127,7 +116,7 @@ fn run_inner(command_request: CommandRequest) -> Result<CommandResult> {
|
||||
|
||||
Ok(CommandResult {
|
||||
status_success: output.status.success(),
|
||||
stdout: output.stdout,
|
||||
stderr: output.stderr,
|
||||
stdout: Rope::from_reader(&output.stdout[..])?,
|
||||
stderr: Rope::from_reader(&output.stderr[..])?,
|
||||
})
|
||||
}
|
||||
|
58
src/main.rs
58
src/main.rs
@ -5,11 +5,13 @@
|
||||
mod command;
|
||||
mod event;
|
||||
mod ui;
|
||||
mod util;
|
||||
use ansi4tui::bytes_to_text;
|
||||
use anyhow::anyhow;
|
||||
use anyhow::bail;
|
||||
use anyhow::Context;
|
||||
use anyhow::Result;
|
||||
use command::CommandCompleted;
|
||||
use command::CommandRequest;
|
||||
use command::CommandResult;
|
||||
use crossbeam::channel::Receiver;
|
||||
@ -18,6 +20,8 @@ use crossterm::event::DisableBracketedPaste;
|
||||
use crossterm::event::EnableBracketedPaste;
|
||||
use event::EventMessage;
|
||||
use parking_lot::RwLock;
|
||||
use ropey::Rope;
|
||||
use std::fs::File;
|
||||
use std::str::FromStr;
|
||||
use std::{
|
||||
io::{self, Write},
|
||||
@ -26,6 +30,10 @@ use std::{
|
||||
thread,
|
||||
time::Duration,
|
||||
};
|
||||
use tracing::instrument;
|
||||
use tracing::Level;
|
||||
use tracing_subscriber::Layer;
|
||||
use tracing_subscriber::{filter, prelude::*};
|
||||
use tui::text::Text;
|
||||
use tui::{
|
||||
backend::{Backend, CrosstermBackend},
|
||||
@ -33,6 +41,8 @@ use tui::{
|
||||
widgets::{Block, Borders, Paragraph, Widget},
|
||||
Frame, Terminal,
|
||||
};
|
||||
use ui::RenderState;
|
||||
use ui::RenderStates;
|
||||
|
||||
use crossterm::{
|
||||
event::{DisableMouseCapture, EnableMouseCapture, Event, KeyCode},
|
||||
@ -69,6 +79,9 @@ pub struct App {
|
||||
/// Original text
|
||||
text_orig: Arc<String>,
|
||||
|
||||
/// Original text (for ui)
|
||||
text_orig_rope: Rope,
|
||||
|
||||
// text_orig_formatted: Arc<String>
|
||||
/// The list of options for a command, given in an RwLock so the command runner can update it
|
||||
command_options: Arc<RwLock<CommandOptions>>,
|
||||
@ -78,6 +91,9 @@ pub struct App {
|
||||
|
||||
/// The command request transmitter
|
||||
command_request_tx: Sender<CommandRequest>,
|
||||
|
||||
/// The rendering state of the stdin/stdout/stderr
|
||||
render_states: RwLock<RenderStates>,
|
||||
}
|
||||
|
||||
impl CommandOptions {
|
||||
@ -118,7 +134,7 @@ impl CommandOptions {
|
||||
..defaults
|
||||
},
|
||||
Template::Perl => Self {
|
||||
cmdline_position: 10_u16,
|
||||
cmdline_position: 9_u16,
|
||||
cmdline: String::from("-p -e 's///'"),
|
||||
..defaults
|
||||
},
|
||||
@ -126,6 +142,7 @@ impl CommandOptions {
|
||||
}
|
||||
}
|
||||
|
||||
// impl App<'_> {
|
||||
impl App {
|
||||
/// Constructs a new instance of `App`
|
||||
///
|
||||
@ -141,11 +158,14 @@ impl App {
|
||||
input: String,
|
||||
template: &Template,
|
||||
) -> Self {
|
||||
let text_orig_rope = Rope::from_str(&input);
|
||||
Self {
|
||||
text_orig: Arc::new(input),
|
||||
text_orig_rope,
|
||||
command_options: Arc::new(RwLock::new(CommandOptions::from_template(template))),
|
||||
message_rx,
|
||||
command_request_tx,
|
||||
render_states: RwLock::new(RenderStates::default()),
|
||||
}
|
||||
}
|
||||
|
||||
@ -176,7 +196,7 @@ impl FromStr for Template {
|
||||
"sed" => Ok(Self::Sed),
|
||||
"awk" => Ok(Self::Awk),
|
||||
"perl" => Ok(Self::Perl),
|
||||
s @ "sh" | s @ "bash" | s @ "zsh" | s @ "dash" => Ok(Self::Sh(s.to_string())),
|
||||
s @ ("sh" | "bash" | "zsh" | "dash") => Ok(Self::Sh(s.to_string())),
|
||||
e => Err(anyhow!("{e} is not a supported command")),
|
||||
}
|
||||
}
|
||||
@ -198,6 +218,8 @@ impl Template {
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
enable_tracing();
|
||||
|
||||
// Error if we aren't getting any stdin
|
||||
if atty::is(atty::Stream::Stdin) {
|
||||
bail!("You must send stdin to this command");
|
||||
@ -249,9 +271,8 @@ fn main() -> Result<()> {
|
||||
let res = res?;
|
||||
|
||||
if let Some(res) = res {
|
||||
std::io::stderr().write_all(res.0.as_bytes())?;
|
||||
std::io::stderr().write_all(res.as_bytes())?;
|
||||
std::io::stderr().write_all(b"\n")?;
|
||||
std::io::stdout().write_all(&res.1)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@ -279,7 +300,9 @@ fn init_message_passing() -> (Receiver<EventMessage>, Sender<CommandRequest>) {
|
||||
(message_rx, command_request_tx)
|
||||
}
|
||||
|
||||
fn run_app<B: Backend>(terminal: &mut Terminal<B>, app: App) -> Result<Option<(String, Vec<u8>)>> {
|
||||
#[allow(clippy::too_many_lines)]
|
||||
#[instrument(skip(terminal, app))]
|
||||
fn run_app<B: Backend>(terminal: &mut Terminal<B>, app: App) -> Result<Option<String>> {
|
||||
// When starting the app, ensure the command runs at least once
|
||||
{
|
||||
let mut command_options = app.command_options.write();
|
||||
@ -294,16 +317,18 @@ fn run_app<B: Backend>(terminal: &mut Terminal<B>, app: App) -> Result<Option<(S
|
||||
}
|
||||
|
||||
loop {
|
||||
tracing::event!(Level::INFO, "Starting draw");
|
||||
terminal.draw(|f| ui::draw(f, &app))?;
|
||||
|
||||
tracing::event!(Level::INFO, "Waiting for event");
|
||||
match app.message_rx.recv()? {
|
||||
EventMessage::CrosstermEvent(crossterm_event) => {
|
||||
tracing::event!(Level::INFO, "Got crossterm event");
|
||||
let mut command_options = app.command_options.write();
|
||||
|
||||
match crossterm_event {
|
||||
Event::Key(key) => match key.code {
|
||||
KeyCode::Esc => {
|
||||
// TODO: If there is any command line text, ask if the user is sure they want to quit
|
||||
return Ok(None);
|
||||
}
|
||||
KeyCode::Char(c) => {
|
||||
@ -346,10 +371,9 @@ fn run_app<B: Backend>(terminal: &mut Terminal<B>, app: App) -> Result<Option<(S
|
||||
command_options.cmdline_position = 0_u16;
|
||||
}
|
||||
KeyCode::Enter => {
|
||||
// TODO: Do not clone here
|
||||
return Ok(Some((
|
||||
format!("{} {}", command_options.command, command_options.cmdline),
|
||||
command_options.command_result.stdout.clone(),
|
||||
return Ok(Some(format!(
|
||||
"{} {}",
|
||||
command_options.command, command_options.cmdline
|
||||
)));
|
||||
}
|
||||
KeyCode::Left => {
|
||||
@ -384,12 +408,13 @@ fn run_app<B: Backend>(terminal: &mut Terminal<B>, app: App) -> Result<Option<(S
|
||||
}
|
||||
|
||||
EventMessage::CommandCompleted(command_completed) => {
|
||||
tracing::event!(Level::INFO, "Got command completed event event");
|
||||
let mut command_options = app.command_options.write();
|
||||
match command_completed {
|
||||
command::CommandCompleted::Success(c) => {
|
||||
CommandCompleted::Success(c) => {
|
||||
command_options.command_result = c;
|
||||
}
|
||||
command::CommandCompleted::Failure(stderr) => {
|
||||
CommandCompleted::Failure(stderr) => {
|
||||
command_options.command_result.status_success = false;
|
||||
command_options.command_result.stderr = stderr;
|
||||
}
|
||||
@ -403,3 +428,12 @@ fn run_app<B: Backend>(terminal: &mut Terminal<B>, app: App) -> Result<Option<(S
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn enable_tracing() {
|
||||
let file = File::create("debug.log").unwrap();
|
||||
let debug_log = tracing_subscriber::fmt::layer().with_writer(Arc::new(file));
|
||||
|
||||
tracing_subscriber::registry()
|
||||
.with(debug_log.with_filter(filter::LevelFilter::INFO))
|
||||
.init();
|
||||
}
|
||||
|
126
src/ui.rs
126
src/ui.rs
@ -1,4 +1,5 @@
|
||||
use crate::command;
|
||||
use crate::util;
|
||||
use crate::App;
|
||||
use ansi4tui::bytes_to_text;
|
||||
use anyhow::anyhow;
|
||||
@ -7,6 +8,9 @@ use anyhow::Result;
|
||||
use command::CommandResult;
|
||||
use crossterm::event::DisableBracketedPaste;
|
||||
use crossterm::event::EnableBracketedPaste;
|
||||
use ropey::RopeSlice;
|
||||
use std::cmp::min;
|
||||
use std::io::Read;
|
||||
use std::str::FromStr;
|
||||
use std::{
|
||||
io::{self, Write},
|
||||
@ -15,6 +19,9 @@ use std::{
|
||||
thread,
|
||||
time::Duration,
|
||||
};
|
||||
use tracing::event;
|
||||
use tracing::instrument;
|
||||
use tracing::Level;
|
||||
use tui::text::Text;
|
||||
use tui::{
|
||||
backend::{Backend, CrosstermBackend},
|
||||
@ -24,13 +31,16 @@ use tui::{
|
||||
};
|
||||
|
||||
use crossterm::{
|
||||
event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode},
|
||||
event::{DisableMouseCapture, EnableMouseCapture, Event, KeyCode},
|
||||
execute,
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
};
|
||||
|
||||
#[instrument(skip(f, app))]
|
||||
pub fn draw<B: Backend>(f: &mut Frame<B>, app: &App) {
|
||||
event!(Level::INFO, "Acquiring lock");
|
||||
let command_options = app.command_options.read();
|
||||
let mut render_states = app.render_states.write();
|
||||
|
||||
let vertical_chunks = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
@ -42,11 +52,16 @@ pub fn draw<B: Backend>(f: &mut Frame<B>, app: &App) {
|
||||
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref())
|
||||
.split(vertical_chunks[0]);
|
||||
|
||||
f.render_widget(
|
||||
Paragraph::new(app.text_orig.as_str())
|
||||
.block(Block::default().title("Orig").borders(Borders::ALL)),
|
||||
event!(Level::INFO, "Rendering orig");
|
||||
// lazy_render_rope_slice(chunks[0], render_states.stdout)
|
||||
render_states.stdout = lazy_render_rope_slice(
|
||||
f,
|
||||
chunks[0],
|
||||
render_states.stdin.as_ref(),
|
||||
app.text_orig_rope.slice(..),
|
||||
"Output",
|
||||
);
|
||||
event!(Level::INFO, "Rendering textbox");
|
||||
f.render_widget(
|
||||
Paragraph::new(command_options.cmdline.as_ref()).block(
|
||||
Block::default()
|
||||
@ -66,7 +81,12 @@ pub fn draw<B: Backend>(f: &mut Frame<B>, app: &App) {
|
||||
);
|
||||
|
||||
// Render the output in the outpout region
|
||||
ui_output(f, chunks[1], &command_options.command_result);
|
||||
ui_output(
|
||||
f,
|
||||
chunks[1],
|
||||
&mut render_states,
|
||||
&command_options.command_result,
|
||||
);
|
||||
|
||||
f.set_cursor(
|
||||
vertical_chunks[1].x + command_options.cmdline_position + 1,
|
||||
@ -74,12 +94,21 @@ pub fn draw<B: Backend>(f: &mut Frame<B>, app: &App) {
|
||||
);
|
||||
}
|
||||
|
||||
fn ui_output<B: Backend>(f: &mut Frame<B>, output_region: Rect, command_result: &CommandResult) {
|
||||
#[instrument(skip(f, command_result))]
|
||||
fn ui_output<B: Backend>(
|
||||
f: &mut Frame<B>,
|
||||
output_region: Rect,
|
||||
render_states: &mut RenderStates,
|
||||
command_result: &CommandResult,
|
||||
) {
|
||||
if command_result.status_success {
|
||||
f.render_widget(
|
||||
Paragraph::new(bytes_to_text(&command_result.stdout))
|
||||
.block(Block::default().title("Output").borders(Borders::ALL)),
|
||||
event!(Level::INFO, "Rendering output");
|
||||
render_states.stdout = lazy_render_rope_slice(
|
||||
f,
|
||||
output_region,
|
||||
render_states.stdout.as_ref(),
|
||||
command_result.stdout.slice(..),
|
||||
"Output",
|
||||
);
|
||||
} else {
|
||||
let chunks = Layout::default()
|
||||
@ -87,15 +116,82 @@ fn ui_output<B: Backend>(f: &mut Frame<B>, output_region: Rect, command_result:
|
||||
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)])
|
||||
.split(output_region);
|
||||
|
||||
f.render_widget(
|
||||
Paragraph::new(bytes_to_text(&command_result.stdout))
|
||||
.block(Block::default().title("Output").borders(Borders::ALL)),
|
||||
event!(Level::INFO, "Rendering output (split)");
|
||||
render_states.stdout = lazy_render_rope_slice(
|
||||
f,
|
||||
chunks[0],
|
||||
render_states.stdout.as_ref(),
|
||||
command_result.stdout.slice(..),
|
||||
"Output",
|
||||
);
|
||||
f.render_widget(
|
||||
Paragraph::new(bytes_to_text(&command_result.stderr))
|
||||
.block(Block::default().title("Error").borders(Borders::ALL)),
|
||||
|
||||
event!(Level::INFO, "Rendering err");
|
||||
render_states.stderr = lazy_render_rope_slice(
|
||||
f,
|
||||
chunks[1],
|
||||
render_states.stderr.as_ref(),
|
||||
command_result.stderr.slice(..),
|
||||
"Stderr",
|
||||
);
|
||||
|
||||
// f.render_widget(
|
||||
// Paragraph::new(bytes_to_text(&command_result.stdout.bytes().collect()))
|
||||
// .block(Block::default().title("Output").borders(Borders::ALL)),
|
||||
// chunks[0],
|
||||
// );
|
||||
// f.render_widget(
|
||||
// Paragraph::new(bytes_to_text(&command_result.stderr.bytes().collect()))
|
||||
// .block(Block::default().title("Error").borders(Borders::ALL)),
|
||||
// chunks[1],
|
||||
// );
|
||||
}
|
||||
event!(Level::INFO, "Done rendering out");
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct RenderStates {
|
||||
pub stdin: Option<RenderState>,
|
||||
pub stdout: Option<RenderState>,
|
||||
pub stderr: Option<RenderState>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
pub struct RenderState {
|
||||
pub size: Rect,
|
||||
pub input_hash: u64,
|
||||
}
|
||||
|
||||
#[instrument(skip(f, data))]
|
||||
fn lazy_render_rope_slice<'a, B: Backend>(
|
||||
f: &mut Frame<B>,
|
||||
output: Rect,
|
||||
previous_state: Option<&RenderState>,
|
||||
data: RopeSlice<'_>,
|
||||
block_title: &'static str,
|
||||
) -> Option<RenderState> {
|
||||
let data = data
|
||||
.lines()
|
||||
.take(output.height as usize)
|
||||
.flat_map(|l| l.bytes().collect::<Vec<u8>>())
|
||||
.collect::<Vec<u8>>();
|
||||
|
||||
let current_state = RenderState {
|
||||
size: output,
|
||||
input_hash: util::hash_bytes(&data),
|
||||
};
|
||||
|
||||
if let Some(ps) = previous_state {
|
||||
if ¤t_state == ps {
|
||||
event!(Level::INFO, "Not rendering: {current_state:?}");
|
||||
// return Some(current_state);
|
||||
}
|
||||
}
|
||||
|
||||
f.render_widget(
|
||||
Paragraph::new(bytes_to_text(&data))
|
||||
.block(Block::default().title(block_title).borders(Borders::ALL)),
|
||||
output,
|
||||
);
|
||||
|
||||
Some(current_state)
|
||||
}
|
||||
|
11
src/util.rs
Normal file
11
src/util.rs
Normal file
@ -0,0 +1,11 @@
|
||||
use std::collections::hash_map::DefaultHasher;
|
||||
use std::hash::Hash;
|
||||
use std::hash::Hasher;
|
||||
|
||||
use ropey::RopeSlice;
|
||||
|
||||
pub fn hash_bytes(r: &[u8]) -> u64 {
|
||||
let mut s = DefaultHasher::new();
|
||||
r.hash(&mut s);
|
||||
s.finish()
|
||||
}
|
Loading…
Reference in New Issue
Block a user