summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorEllie Huxtable <ellie@elliehuxtable.com>2024-04-22 09:44:14 +0100
committerEllie Huxtable <ellie@elliehuxtable.com>2024-04-22 09:44:14 +0100
commitc084fad4e77f5ed29f9eeed8ab8c21610ce4cccb (patch)
treec910f76007e6c353fc1fc652bfbb0968fb1f7081
parent99e1c1d358a73c87907800d8cca288caa51d5436 (diff)
wip pty stuff
-rw-r--r--Cargo.lock158
-rw-r--r--crates/atuin-run/Cargo.toml3
-rw-r--r--crates/atuin-run/src/lib.rs2
-rw-r--r--crates/atuin-run/src/pty.rs96
-rw-r--r--crates/atuin/src/command/client/run.rs5
5 files changed, 261 insertions, 3 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 3c5eaa4f..cef6f4f7 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -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(())
}