mirror of
https://github.com/rust-kotlin/ashell.git
synced 2026-10-30 08:06:06 +00:00
143 lines
4.5 KiB
Rust
143 lines
4.5 KiB
Rust
use std::{
|
|
io::{Read, Write},
|
|
sync::mpsc::{self, Sender},
|
|
thread,
|
|
};
|
|
|
|
use anyhow::{Context, Result};
|
|
use portable_pty::{CommandBuilder, PtySize, native_pty_system};
|
|
|
|
use crate::terminal::{BackendCommand, BackendEvent, BackendTx};
|
|
|
|
pub fn spawn_local_terminal(
|
|
tab_id: String,
|
|
cols: u16,
|
|
rows: u16,
|
|
events: Sender<BackendEvent>,
|
|
) -> Result<BackendTx> {
|
|
let pty_system = native_pty_system();
|
|
let pair = pty_system
|
|
.openpty(PtySize {
|
|
rows,
|
|
cols,
|
|
pixel_width: 0,
|
|
pixel_height: 0,
|
|
})
|
|
.context("open local PTY")?;
|
|
|
|
let shell = std::env::var("SHELL").unwrap_or_else(|_| {
|
|
if cfg!(windows) {
|
|
"powershell.exe".into()
|
|
} else {
|
|
"/bin/zsh".into()
|
|
}
|
|
});
|
|
|
|
let mut cmd = CommandBuilder::new(&shell);
|
|
cmd.env(
|
|
"TERM",
|
|
std::env::var("TERM").unwrap_or_else(|_| "xterm-256color".into()),
|
|
);
|
|
cmd.env(
|
|
"COLORTERM",
|
|
std::env::var("COLORTERM").unwrap_or_else(|_| "truecolor".into()),
|
|
);
|
|
cmd.env("TERM_PROGRAM", "ashell");
|
|
if let Ok(path) = std::env::var("PATH") {
|
|
cmd.env("PATH", path);
|
|
}
|
|
if let Ok(lang) = std::env::var("LANG") {
|
|
cmd.env("LANG", lang);
|
|
} else {
|
|
cmd.env("LANG", "en_US.UTF-8");
|
|
}
|
|
if let Ok(home) = std::env::var("HOME") {
|
|
cmd.env("HOME", home);
|
|
}
|
|
cmd.env("SHELL", shell);
|
|
let mut child = pair.slave.spawn_command(cmd).context("spawn local shell")?;
|
|
drop(pair.slave);
|
|
|
|
let master = pair.master;
|
|
let mut reader = master.try_clone_reader().context("clone PTY reader")?;
|
|
let mut writer = master.take_writer().context("take PTY writer")?;
|
|
let (cmd_tx, cmd_rx) = mpsc::channel::<BackendCommand>();
|
|
|
|
let read_tab = tab_id.clone();
|
|
let read_events = events.clone();
|
|
thread::spawn(move || {
|
|
let mut buf = [0u8; 8192];
|
|
loop {
|
|
match reader.read(&mut buf) {
|
|
Ok(0) => break,
|
|
Ok(n) => {
|
|
let _ = read_events.send(BackendEvent::Output {
|
|
tab_id: read_tab.clone(),
|
|
bytes: buf[..n].to_vec(),
|
|
});
|
|
}
|
|
Err(err) => {
|
|
let _ = read_events.send(BackendEvent::Closed {
|
|
tab_id: read_tab.clone(),
|
|
reason: format!("local read error: {err}"),
|
|
});
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
let _ = read_events.send(BackendEvent::Closed {
|
|
tab_id: read_tab,
|
|
reason: "local shell closed".into(),
|
|
});
|
|
});
|
|
|
|
let write_tab = tab_id.clone();
|
|
let write_events = events.clone();
|
|
thread::spawn(move || {
|
|
loop {
|
|
match cmd_rx.recv_timeout(std::time::Duration::from_millis(100)) {
|
|
Ok(command) => match command {
|
|
BackendCommand::Input(bytes) => {
|
|
if let Err(err) = writer.write_all(&bytes) {
|
|
let _ = write_events.send(BackendEvent::Closed {
|
|
tab_id: write_tab.clone(),
|
|
reason: format!("local write error: {err}"),
|
|
});
|
|
break;
|
|
}
|
|
let _ = writer.flush();
|
|
}
|
|
BackendCommand::Resize { cols, rows } => {
|
|
let _ = master.resize(PtySize {
|
|
rows,
|
|
cols,
|
|
pixel_width: 0,
|
|
pixel_height: 0,
|
|
});
|
|
}
|
|
BackendCommand::Close => break,
|
|
BackendCommand::SampleMetrics => {}
|
|
},
|
|
Err(mpsc::RecvTimeoutError::Timeout) => {
|
|
if let Ok(Some(status)) = child.try_wait() {
|
|
let _ = write_events.send(BackendEvent::Closed {
|
|
tab_id: write_tab,
|
|
reason: format!("local shell exited: {status}"),
|
|
});
|
|
return;
|
|
}
|
|
}
|
|
Err(mpsc::RecvTimeoutError::Disconnected) => break,
|
|
}
|
|
}
|
|
let _ = child.kill();
|
|
});
|
|
|
|
let _ = events.send(BackendEvent::Status {
|
|
tab_id,
|
|
text: "local shell ready".into(),
|
|
});
|
|
|
|
Ok(BackendTx::Local(cmd_tx))
|
|
}
|