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",
|
"crossbeam",
|
||||||
"crossterm",
|
"crossterm",
|
||||||
"parking_lot",
|
"parking_lot",
|
||||||
|
"ropey",
|
||||||
"shellwords",
|
"shellwords",
|
||||||
|
"tracing",
|
||||||
|
"tracing-subscriber",
|
||||||
"tui",
|
"tui",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -364,6 +367,16 @@ dependencies = [
|
|||||||
"version_check",
|
"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]]
|
[[package]]
|
||||||
name = "num-derive"
|
name = "num-derive"
|
||||||
version = "0.3.3"
|
version = "0.3.3"
|
||||||
@ -384,6 +397,12 @@ dependencies = [
|
|||||||
"autocfg",
|
"autocfg",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "once_cell"
|
||||||
|
version = "1.16.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "opaque-debug"
|
name = "opaque-debug"
|
||||||
version = "0.3.0"
|
version = "0.3.0"
|
||||||
@ -399,6 +418,12 @@ dependencies = [
|
|||||||
"num-traits",
|
"num-traits",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "overload"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "parking_lot"
|
name = "parking_lot"
|
||||||
version = "0.12.1"
|
version = "0.12.1"
|
||||||
@ -470,6 +495,12 @@ dependencies = [
|
|||||||
"siphasher",
|
"siphasher",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pin-project-lite"
|
||||||
|
version = "0.2.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ppv-lite86"
|
name = "ppv-lite86"
|
||||||
version = "0.2.17"
|
version = "0.2.17"
|
||||||
@ -582,6 +613,16 @@ version = "0.6.28"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848"
|
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]]
|
[[package]]
|
||||||
name = "scopeguard"
|
name = "scopeguard"
|
||||||
version = "1.1.0"
|
version = "1.1.0"
|
||||||
@ -619,6 +660,15 @@ dependencies = [
|
|||||||
"opaque-debug",
|
"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]]
|
[[package]]
|
||||||
name = "shellwords"
|
name = "shellwords"
|
||||||
version = "1.1.0"
|
version = "1.1.0"
|
||||||
@ -681,6 +731,12 @@ version = "1.10.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
|
checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "str_indices"
|
||||||
|
version = "0.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5f026164926842ec52deb1938fae44f83dfdb82d0a5b0270c5bd5935ab74d6dd"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "1.0.105"
|
version = "1.0.105"
|
||||||
@ -766,6 +822,73 @@ dependencies = [
|
|||||||
"syn",
|
"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]]
|
[[package]]
|
||||||
name = "tui"
|
name = "tui"
|
||||||
version = "0.19.0"
|
version = "0.19.0"
|
||||||
@ -815,6 +938,12 @@ version = "0.2.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "936e4b492acfd135421d8dca4b1aa80a7bfc26e702ef3af710e0752684df5372"
|
checksum = "936e4b492acfd135421d8dca4b1aa80a7bfc26e702ef3af710e0752684df5372"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "valuable"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "version_check"
|
name = "version_check"
|
||||||
version = "0.9.4"
|
version = "0.9.4"
|
||||||
|
@ -14,3 +14,6 @@ anyhow = "1.0.66"
|
|||||||
shellwords = "1.1.0"
|
shellwords = "1.1.0"
|
||||||
crossbeam = "0.8.2"
|
crossbeam = "0.8.2"
|
||||||
parking_lot = "0.12.1"
|
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::event::EventMessage;
|
||||||
use crate::CommandOptions;
|
use crate::CommandOptions;
|
||||||
|
use ansi4tui::bytes_to_text;
|
||||||
use anyhow::bail;
|
use anyhow::bail;
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use crossbeam::channel::Receiver;
|
use crossbeam::channel::Receiver;
|
||||||
use crossbeam::channel::Sender;
|
use crossbeam::channel::Sender;
|
||||||
use parking_lot::RwLock;
|
use parking_lot::RwLock;
|
||||||
|
use ropey::Rope;
|
||||||
|
use std::collections::hash_map::DefaultHasher;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::{
|
use std::{
|
||||||
io::Write,
|
io::Write,
|
||||||
process::{Command, Stdio},
|
process::{Command, Stdio},
|
||||||
};
|
};
|
||||||
|
use tui::widgets::Paragraph;
|
||||||
|
|
||||||
pub type CommandRequest = (Arc<RwLock<CommandOptions>>, Arc<String>);
|
pub type CommandRequest = (Arc<RwLock<CommandOptions>>, Arc<String>);
|
||||||
use crate::App;
|
use crate::App;
|
||||||
@ -18,22 +22,24 @@ use crate::App;
|
|||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum CommandCompleted {
|
pub enum CommandCompleted {
|
||||||
Success(CommandResult),
|
Success(CommandResult),
|
||||||
Failure(Vec<u8>),
|
Failure(Rope),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct CommandResult {
|
pub struct CommandResult {
|
||||||
pub status_success: bool,
|
pub status_success: bool,
|
||||||
pub stdout: Vec<u8>,
|
pub stdout: Rope,
|
||||||
pub stderr: Vec<u8>,
|
pub stderr: Rope,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Result?
|
||||||
impl Default for CommandResult {
|
impl Default for CommandResult {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
status_success: true,
|
status_success: true,
|
||||||
stdout: vec![],
|
// TODO: Option<Rope>
|
||||||
stderr: vec![],
|
stdout: Rope::from_str(""),
|
||||||
|
stderr: Rope::from_str(""),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -49,13 +55,14 @@ pub fn command_event_loop(
|
|||||||
match run_inner(command_request) {
|
match run_inner(command_request) {
|
||||||
Ok(c) => {
|
Ok(c) => {
|
||||||
// If there was no stdout and the command failed, don't touch stdout
|
// 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)
|
CommandCompleted::Failure(c.stderr)
|
||||||
} else {
|
} else {
|
||||||
|
// CommandCompleted::Success((c, FormattedCommandResult::from(&c)))
|
||||||
CommandCompleted::Success(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> {
|
fn run_inner(command_request: CommandRequest) -> Result<CommandResult> {
|
||||||
// Spawn the child
|
// Spawn the child
|
||||||
let mut child = {
|
let mut child = {
|
||||||
@ -127,7 +116,7 @@ fn run_inner(command_request: CommandRequest) -> Result<CommandResult> {
|
|||||||
|
|
||||||
Ok(CommandResult {
|
Ok(CommandResult {
|
||||||
status_success: output.status.success(),
|
status_success: output.status.success(),
|
||||||
stdout: output.stdout,
|
stdout: Rope::from_reader(&output.stdout[..])?,
|
||||||
stderr: output.stderr,
|
stderr: Rope::from_reader(&output.stderr[..])?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
58
src/main.rs
58
src/main.rs
@ -5,11 +5,13 @@
|
|||||||
mod command;
|
mod command;
|
||||||
mod event;
|
mod event;
|
||||||
mod ui;
|
mod ui;
|
||||||
|
mod util;
|
||||||
use ansi4tui::bytes_to_text;
|
use ansi4tui::bytes_to_text;
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
use anyhow::bail;
|
use anyhow::bail;
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use command::CommandCompleted;
|
||||||
use command::CommandRequest;
|
use command::CommandRequest;
|
||||||
use command::CommandResult;
|
use command::CommandResult;
|
||||||
use crossbeam::channel::Receiver;
|
use crossbeam::channel::Receiver;
|
||||||
@ -18,6 +20,8 @@ use crossterm::event::DisableBracketedPaste;
|
|||||||
use crossterm::event::EnableBracketedPaste;
|
use crossterm::event::EnableBracketedPaste;
|
||||||
use event::EventMessage;
|
use event::EventMessage;
|
||||||
use parking_lot::RwLock;
|
use parking_lot::RwLock;
|
||||||
|
use ropey::Rope;
|
||||||
|
use std::fs::File;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::{
|
use std::{
|
||||||
io::{self, Write},
|
io::{self, Write},
|
||||||
@ -26,6 +30,10 @@ use std::{
|
|||||||
thread,
|
thread,
|
||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
|
use tracing::instrument;
|
||||||
|
use tracing::Level;
|
||||||
|
use tracing_subscriber::Layer;
|
||||||
|
use tracing_subscriber::{filter, prelude::*};
|
||||||
use tui::text::Text;
|
use tui::text::Text;
|
||||||
use tui::{
|
use tui::{
|
||||||
backend::{Backend, CrosstermBackend},
|
backend::{Backend, CrosstermBackend},
|
||||||
@ -33,6 +41,8 @@ use tui::{
|
|||||||
widgets::{Block, Borders, Paragraph, Widget},
|
widgets::{Block, Borders, Paragraph, Widget},
|
||||||
Frame, Terminal,
|
Frame, Terminal,
|
||||||
};
|
};
|
||||||
|
use ui::RenderState;
|
||||||
|
use ui::RenderStates;
|
||||||
|
|
||||||
use crossterm::{
|
use crossterm::{
|
||||||
event::{DisableMouseCapture, EnableMouseCapture, Event, KeyCode},
|
event::{DisableMouseCapture, EnableMouseCapture, Event, KeyCode},
|
||||||
@ -69,6 +79,9 @@ pub struct App {
|
|||||||
/// Original text
|
/// Original text
|
||||||
text_orig: Arc<String>,
|
text_orig: Arc<String>,
|
||||||
|
|
||||||
|
/// Original text (for ui)
|
||||||
|
text_orig_rope: Rope,
|
||||||
|
|
||||||
// text_orig_formatted: Arc<String>
|
// text_orig_formatted: Arc<String>
|
||||||
/// The list of options for a command, given in an RwLock so the command runner can update it
|
/// The list of options for a command, given in an RwLock so the command runner can update it
|
||||||
command_options: Arc<RwLock<CommandOptions>>,
|
command_options: Arc<RwLock<CommandOptions>>,
|
||||||
@ -78,6 +91,9 @@ pub struct App {
|
|||||||
|
|
||||||
/// The command request transmitter
|
/// The command request transmitter
|
||||||
command_request_tx: Sender<CommandRequest>,
|
command_request_tx: Sender<CommandRequest>,
|
||||||
|
|
||||||
|
/// The rendering state of the stdin/stdout/stderr
|
||||||
|
render_states: RwLock<RenderStates>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CommandOptions {
|
impl CommandOptions {
|
||||||
@ -118,7 +134,7 @@ impl CommandOptions {
|
|||||||
..defaults
|
..defaults
|
||||||
},
|
},
|
||||||
Template::Perl => Self {
|
Template::Perl => Self {
|
||||||
cmdline_position: 10_u16,
|
cmdline_position: 9_u16,
|
||||||
cmdline: String::from("-p -e 's///'"),
|
cmdline: String::from("-p -e 's///'"),
|
||||||
..defaults
|
..defaults
|
||||||
},
|
},
|
||||||
@ -126,6 +142,7 @@ impl CommandOptions {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// impl App<'_> {
|
||||||
impl App {
|
impl App {
|
||||||
/// Constructs a new instance of `App`
|
/// Constructs a new instance of `App`
|
||||||
///
|
///
|
||||||
@ -141,11 +158,14 @@ impl App {
|
|||||||
input: String,
|
input: String,
|
||||||
template: &Template,
|
template: &Template,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
|
let text_orig_rope = Rope::from_str(&input);
|
||||||
Self {
|
Self {
|
||||||
text_orig: Arc::new(input),
|
text_orig: Arc::new(input),
|
||||||
|
text_orig_rope,
|
||||||
command_options: Arc::new(RwLock::new(CommandOptions::from_template(template))),
|
command_options: Arc::new(RwLock::new(CommandOptions::from_template(template))),
|
||||||
message_rx,
|
message_rx,
|
||||||
command_request_tx,
|
command_request_tx,
|
||||||
|
render_states: RwLock::new(RenderStates::default()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -176,7 +196,7 @@ impl FromStr for Template {
|
|||||||
"sed" => Ok(Self::Sed),
|
"sed" => Ok(Self::Sed),
|
||||||
"awk" => Ok(Self::Awk),
|
"awk" => Ok(Self::Awk),
|
||||||
"perl" => Ok(Self::Perl),
|
"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")),
|
e => Err(anyhow!("{e} is not a supported command")),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -198,6 +218,8 @@ impl Template {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn main() -> Result<()> {
|
fn main() -> Result<()> {
|
||||||
|
enable_tracing();
|
||||||
|
|
||||||
// Error if we aren't getting any stdin
|
// Error if we aren't getting any stdin
|
||||||
if atty::is(atty::Stream::Stdin) {
|
if atty::is(atty::Stream::Stdin) {
|
||||||
bail!("You must send stdin to this command");
|
bail!("You must send stdin to this command");
|
||||||
@ -249,9 +271,8 @@ fn main() -> Result<()> {
|
|||||||
let res = res?;
|
let res = res?;
|
||||||
|
|
||||||
if let Some(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::stderr().write_all(b"\n")?;
|
||||||
std::io::stdout().write_all(&res.1)?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -279,7 +300,9 @@ fn init_message_passing() -> (Receiver<EventMessage>, Sender<CommandRequest>) {
|
|||||||
(message_rx, command_request_tx)
|
(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
|
// When starting the app, ensure the command runs at least once
|
||||||
{
|
{
|
||||||
let mut command_options = app.command_options.write();
|
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 {
|
loop {
|
||||||
|
tracing::event!(Level::INFO, "Starting draw");
|
||||||
terminal.draw(|f| ui::draw(f, &app))?;
|
terminal.draw(|f| ui::draw(f, &app))?;
|
||||||
|
|
||||||
|
tracing::event!(Level::INFO, "Waiting for event");
|
||||||
match app.message_rx.recv()? {
|
match app.message_rx.recv()? {
|
||||||
EventMessage::CrosstermEvent(crossterm_event) => {
|
EventMessage::CrosstermEvent(crossterm_event) => {
|
||||||
|
tracing::event!(Level::INFO, "Got crossterm event");
|
||||||
let mut command_options = app.command_options.write();
|
let mut command_options = app.command_options.write();
|
||||||
|
|
||||||
match crossterm_event {
|
match crossterm_event {
|
||||||
Event::Key(key) => match key.code {
|
Event::Key(key) => match key.code {
|
||||||
KeyCode::Esc => {
|
KeyCode::Esc => {
|
||||||
// TODO: If there is any command line text, ask if the user is sure they want to quit
|
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
KeyCode::Char(c) => {
|
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;
|
command_options.cmdline_position = 0_u16;
|
||||||
}
|
}
|
||||||
KeyCode::Enter => {
|
KeyCode::Enter => {
|
||||||
// TODO: Do not clone here
|
return Ok(Some(format!(
|
||||||
return Ok(Some((
|
"{} {}",
|
||||||
format!("{} {}", command_options.command, command_options.cmdline),
|
command_options.command, command_options.cmdline
|
||||||
command_options.command_result.stdout.clone(),
|
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
KeyCode::Left => {
|
KeyCode::Left => {
|
||||||
@ -384,12 +408,13 @@ fn run_app<B: Backend>(terminal: &mut Terminal<B>, app: App) -> Result<Option<(S
|
|||||||
}
|
}
|
||||||
|
|
||||||
EventMessage::CommandCompleted(command_completed) => {
|
EventMessage::CommandCompleted(command_completed) => {
|
||||||
|
tracing::event!(Level::INFO, "Got command completed event event");
|
||||||
let mut command_options = app.command_options.write();
|
let mut command_options = app.command_options.write();
|
||||||
match command_completed {
|
match command_completed {
|
||||||
command::CommandCompleted::Success(c) => {
|
CommandCompleted::Success(c) => {
|
||||||
command_options.command_result = c;
|
command_options.command_result = c;
|
||||||
}
|
}
|
||||||
command::CommandCompleted::Failure(stderr) => {
|
CommandCompleted::Failure(stderr) => {
|
||||||
command_options.command_result.status_success = false;
|
command_options.command_result.status_success = false;
|
||||||
command_options.command_result.stderr = stderr;
|
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::command;
|
||||||
|
use crate::util;
|
||||||
use crate::App;
|
use crate::App;
|
||||||
use ansi4tui::bytes_to_text;
|
use ansi4tui::bytes_to_text;
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
@ -7,6 +8,9 @@ use anyhow::Result;
|
|||||||
use command::CommandResult;
|
use command::CommandResult;
|
||||||
use crossterm::event::DisableBracketedPaste;
|
use crossterm::event::DisableBracketedPaste;
|
||||||
use crossterm::event::EnableBracketedPaste;
|
use crossterm::event::EnableBracketedPaste;
|
||||||
|
use ropey::RopeSlice;
|
||||||
|
use std::cmp::min;
|
||||||
|
use std::io::Read;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::{
|
use std::{
|
||||||
io::{self, Write},
|
io::{self, Write},
|
||||||
@ -15,6 +19,9 @@ use std::{
|
|||||||
thread,
|
thread,
|
||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
|
use tracing::event;
|
||||||
|
use tracing::instrument;
|
||||||
|
use tracing::Level;
|
||||||
use tui::text::Text;
|
use tui::text::Text;
|
||||||
use tui::{
|
use tui::{
|
||||||
backend::{Backend, CrosstermBackend},
|
backend::{Backend, CrosstermBackend},
|
||||||
@ -24,13 +31,16 @@ use tui::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use crossterm::{
|
use crossterm::{
|
||||||
event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode},
|
event::{DisableMouseCapture, EnableMouseCapture, Event, KeyCode},
|
||||||
execute,
|
execute,
|
||||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[instrument(skip(f, app))]
|
||||||
pub fn draw<B: Backend>(f: &mut Frame<B>, app: &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 command_options = app.command_options.read();
|
||||||
|
let mut render_states = app.render_states.write();
|
||||||
|
|
||||||
let vertical_chunks = Layout::default()
|
let vertical_chunks = Layout::default()
|
||||||
.direction(Direction::Vertical)
|
.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())
|
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref())
|
||||||
.split(vertical_chunks[0]);
|
.split(vertical_chunks[0]);
|
||||||
|
|
||||||
f.render_widget(
|
event!(Level::INFO, "Rendering orig");
|
||||||
Paragraph::new(app.text_orig.as_str())
|
// lazy_render_rope_slice(chunks[0], render_states.stdout)
|
||||||
.block(Block::default().title("Orig").borders(Borders::ALL)),
|
render_states.stdout = lazy_render_rope_slice(
|
||||||
|
f,
|
||||||
chunks[0],
|
chunks[0],
|
||||||
|
render_states.stdin.as_ref(),
|
||||||
|
app.text_orig_rope.slice(..),
|
||||||
|
"Output",
|
||||||
);
|
);
|
||||||
|
event!(Level::INFO, "Rendering textbox");
|
||||||
f.render_widget(
|
f.render_widget(
|
||||||
Paragraph::new(command_options.cmdline.as_ref()).block(
|
Paragraph::new(command_options.cmdline.as_ref()).block(
|
||||||
Block::default()
|
Block::default()
|
||||||
@ -66,7 +81,12 @@ pub fn draw<B: Backend>(f: &mut Frame<B>, app: &App) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Render the output in the outpout region
|
// 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(
|
f.set_cursor(
|
||||||
vertical_chunks[1].x + command_options.cmdline_position + 1,
|
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 {
|
if command_result.status_success {
|
||||||
f.render_widget(
|
event!(Level::INFO, "Rendering output");
|
||||||
Paragraph::new(bytes_to_text(&command_result.stdout))
|
render_states.stdout = lazy_render_rope_slice(
|
||||||
.block(Block::default().title("Output").borders(Borders::ALL)),
|
f,
|
||||||
output_region,
|
output_region,
|
||||||
|
render_states.stdout.as_ref(),
|
||||||
|
command_result.stdout.slice(..),
|
||||||
|
"Output",
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
let chunks = Layout::default()
|
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)])
|
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)])
|
||||||
.split(output_region);
|
.split(output_region);
|
||||||
|
|
||||||
f.render_widget(
|
event!(Level::INFO, "Rendering output (split)");
|
||||||
Paragraph::new(bytes_to_text(&command_result.stdout))
|
render_states.stdout = lazy_render_rope_slice(
|
||||||
.block(Block::default().title("Output").borders(Borders::ALL)),
|
f,
|
||||||
chunks[0],
|
chunks[0],
|
||||||
|
render_states.stdout.as_ref(),
|
||||||
|
command_result.stdout.slice(..),
|
||||||
|
"Output",
|
||||||
);
|
);
|
||||||
f.render_widget(
|
|
||||||
Paragraph::new(bytes_to_text(&command_result.stderr))
|
event!(Level::INFO, "Rendering err");
|
||||||
.block(Block::default().title("Error").borders(Borders::ALL)),
|
render_states.stderr = lazy_render_rope_slice(
|
||||||
|
f,
|
||||||
chunks[1],
|
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