diff options
author | Ellie Huxtable <ellie@elliehuxtable.com> | 2024-04-22 09:44:14 +0100 |
---|---|---|
committer | Ellie Huxtable <ellie@elliehuxtable.com> | 2024-04-22 09:44:14 +0100 |
commit | c084fad4e77f5ed29f9eeed8ab8c21610ce4cccb (patch) | |
tree | c910f76007e6c353fc1fc652bfbb0968fb1f7081 | |
parent | 99e1c1d358a73c87907800d8cca288caa51d5436 (diff) |
wip pty stuff
-rw-r--r-- | Cargo.lock | 158 | ||||
-rw-r--r-- | crates/atuin-run/Cargo.toml | 3 | ||||
-rw-r--r-- | crates/atuin-run/src/lib.rs | 2 | ||||
-rw-r--r-- | crates/atuin-run/src/pty.rs | 96 | ||||
-rw-r--r-- | crates/atuin/src/command/client/run.rs | 5 |
5 files changed, 261 insertions, 3 deletions
@@ -143,6 +143,12 @@ dependencies = [ ] [[package]] +name = "arrayvec" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" + +[[package]] name = "async-trait" version = "0.1.80" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -312,6 +318,9 @@ name = "atuin-run" version = "0.1.0" dependencies = [ "comrak", + "eyre", + "portable-pty", + "vt100", ] [[package]] @@ -1966,6 +1975,15 @@ dependencies = [ ] [[package]] +name = "ioctl-rs" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7970510895cee30b3e9128319f2cefd4bde883a39f38baa279567ba3a7eb97d" +dependencies = [ + "libc", +] + +[[package]] name = "ipnet" version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2287,6 +2305,20 @@ dependencies = [ [[package]] name = "nix" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f346ff70e7dbfd675fe90590b92d59ef2de15a8779ae305ebcbfd3f0caf59be4" +dependencies = [ + "autocfg", + "bitflags 1.3.2", + "cfg-if", + "libc", + "memoffset", + "pin-utils", +] + +[[package]] +name = "nix" version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4" @@ -2748,6 +2780,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7170ef9988bc169ba16dd36a7fa041e5c4cbeb6a35b76d4c03daded371eae7c0" [[package]] +name = "portable-pty" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "806ee80c2a03dbe1a9fb9534f8d19e4c0546b790cde8fd1fea9d6390644cb0be" +dependencies = [ + "anyhow", + "bitflags 1.3.2", + "downcast-rs", + "filedescriptor", + "lazy_static", + "libc", + "log", + "nix 0.25.1", + "serial", + "shared_library", + "shell-words", + "winapi", + "winreg 0.10.1", +] + +[[package]] name = "powerfmt" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2992,7 +3045,7 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "winreg", + "winreg 0.50.0", ] [[package]] @@ -3396,6 +3449,48 @@ dependencies = [ ] [[package]] +name = "serial" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1237a96570fc377c13baa1b88c7589ab66edced652e43ffb17088f003db3e86" +dependencies = [ + "serial-core", + "serial-unix", + "serial-windows", +] + +[[package]] +name = "serial-core" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f46209b345401737ae2125fe5b19a77acce90cd53e1658cda928e4fe9a64581" +dependencies = [ + "libc", +] + +[[package]] +name = "serial-unix" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f03fbca4c9d866e24a459cbca71283f545a37f8e3e002ad8c70593871453cab7" +dependencies = [ + "ioctl-rs", + "libc", + "serial-core", + "termios", +] + +[[package]] +name = "serial-windows" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15c6d3b776267a75d31bbdfd5d36c0ca051251caafc285827052bc53bcdc8162" +dependencies = [ + "libc", + "serial-core", +] + +[[package]] name = "sha1" version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -3427,6 +3522,16 @@ dependencies = [ ] [[package]] +name = "shared_library" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a9e7e0f2bfae24d8a5b5a66c5b257a83c7412304311512a0c054cd5e619da11" +dependencies = [ + "lazy_static", + "libc", +] + +[[package]] name = "shell-words" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -3960,6 +4065,15 @@ dependencies = [ ] [[package]] +name = "termios" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5d9cf598a6d7ce700a4e6a9199da127e6819a61e64b68609683cc9a01b5683a" +dependencies = [ + "libc", +] + +[[package]] name = "thiserror" version = "1.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -4413,6 +4527,39 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] +name = "vt100" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84cd863bf0db7e392ba3bd04994be3473491b31e66340672af5d11943c6274de" +dependencies = [ + "itoa", + "log", + "unicode-width", + "vte", +] + +[[package]] +name = "vte" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5022b5fbf9407086c180e9557be968742d839e68346af7792b8592489732197" +dependencies = [ + "arrayvec", + "utf8parse", + "vte_generate_state_changes", +] + +[[package]] +name = "vte_generate_state_changes" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d257817081c7dffcdbab24b9e62d2def62e2ff7d00b1c20062551e6cccc145ff" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] name = "walkdir" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -4788,6 +4935,15 @@ checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" [[package]] name = "winreg" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" +dependencies = [ + "winapi", +] + +[[package]] +name = "winreg" version = "0.50.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" diff --git a/crates/atuin-run/Cargo.toml b/crates/atuin-run/Cargo.toml index 12f0edcf..41207780 100644 --- a/crates/atuin-run/Cargo.toml +++ b/crates/atuin-run/Cargo.toml @@ -12,4 +12,7 @@ readme.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +eyre.workspace = true comrak = "0.22" +portable-pty = "0.8.1" +vt100 = "0.15.2" diff --git a/crates/atuin-run/src/lib.rs b/crates/atuin-run/src/lib.rs index 2622fa19..5ccce8a7 100644 --- a/crates/atuin-run/src/lib.rs +++ b/crates/atuin-run/src/lib.rs @@ -1,2 +1,2 @@ pub mod markdown; - +pub mod pty; diff --git a/crates/atuin-run/src/pty.rs b/crates/atuin-run/src/pty.rs new file mode 100644 index 00000000..998dfa34 --- /dev/null +++ b/crates/atuin-run/src/pty.rs @@ -0,0 +1,96 @@ +/// Create and manage pseudoterminals +use eyre::Result; +use portable_pty::{CommandBuilder, NativePtySystem, PtySize, PtySystem}; +use std::sync::mpsc::channel; +use vt100::Screen; + +/// Run a command in a pty, return output. Pty is closed once the command has completed. +/// If a child process would work, prefer that approach - this is a bit slower and heavier. +pub fn run_pty() -> Result<String> { + let pty_system = NativePtySystem::default(); + + let pair = pty_system + .openpty(PtySize { + rows: 24, + cols: 80, + pixel_width: 0, + pixel_height: 0, + }) + .unwrap(); + + let cmd = CommandBuilder::new("bash"); + let mut child = pair.slave.spawn_command(cmd).unwrap(); + + // Release any handles owned by the slave: we don't need it now + // that we've spawned the child. + drop(pair.slave); + + // Read the output in another thread. + // This is important because it is easy to encounter a situation + // where read/write buffers fill and block either your process + // or the spawned process. + let (tx, rx) = channel(); + let mut reader = pair.master.try_clone_reader().unwrap(); + + std::thread::spawn(move || { + // Consume the output from the child + let mut s = String::new(); + reader.read_to_string(&mut s).unwrap(); + tx.send(s).unwrap(); + }); + + { + // Obtain the writer. + // When the writer is dropped, EOF will be sent to + // the program that was spawned. + // It is important to take the writer even if you don't + // send anything to its stdin so that EOF can be + // generated, otherwise you risk deadlocking yourself. + let mut writer = pair.master.take_writer().unwrap(); + + if cfg!(target_os = "macos") { + // macOS quirk: the child and reader must be started and + // allowed a brief grace period to run before we allow + // the writer to drop. Otherwise, the data we send to + // the kernel to trigger EOF is interleaved with the + // data read by the reader! WTF!? + // This appears to be a race condition for very short + // lived processes on macOS. + // I'd love to find a more deterministic solution to + // this than sleeping. + std::thread::sleep(std::time::Duration::from_millis(20)); + } + + // This example doesn't need to write anything, but if you + // want to send data to the child, you'd set `to_write` to + // that data and do it like this: + let to_write = "echo 'omg the pty DID SOMETHING'\r\nexit\r\n"; + if !to_write.is_empty() { + // To avoid deadlock, wrt. reading and waiting, we send + // data to the stdin of the child in a different thread. + std::thread::spawn(move || { + writer.write_all(to_write.as_bytes()).unwrap(); + }); + } + } + + // Wait for the child to complete + println!("child status: {:?}", child.wait().unwrap()); + + // Take care to drop the master after our processes are + // done, as some platforms get unhappy if it is dropped + // sooner than that. + drop(pair.master); + + // Now wait for the output to be read by our reader thread + let output = rx.recv().unwrap(); + + // We print with escapes escaped because the windows conpty + // implementation synthesizes title change escape sequences + // in the output stream and it can be confusing to see those + // printed out raw in another terminal. + let out = output.to_string(); + println!("{out}"); + + Ok("".to_string()) +} diff --git a/crates/atuin/src/command/client/run.rs b/crates/atuin/src/command/client/run.rs index f749c419..4dcdf18b 100644 --- a/crates/atuin/src/command/client/run.rs +++ b/crates/atuin/src/command/client/run.rs @@ -1,6 +1,6 @@ use eyre::Result; -use atuin_run::markdown::parse; +use atuin_run::{markdown::parse, pty::run_pty}; pub fn run() -> Result<()> { let blocks = parse( @@ -18,5 +18,8 @@ echo 'bar' ); println!("{:?}", blocks); + + run_pty(); + Ok(()) } |