1
1
mirror of https://github.com/wez/wezterm.git synced 2024-11-27 12:23:46 +03:00
wezterm/wezterm-ssh/examples/ssh.rs
Wez Furlong 225e7a1243 introduce unicode_version config
This is a fairly far-reaching commit. The idea is:

* Introduce a unicode_version config that specifies the default level
  of unicode conformance for each newly created Terminal (each Pane)
* The unicode_version is passed down to the `grapheme_column_width`
  function which interprets the width based on the version
* `Cell` records the width so that later calculations don't need to
  know the unicode version

In a subsequent diff, I will introduce an escape sequence that allows
setting/pushing/popping the unicode version so that it can be overridden
via eg: a shell alias prior to launching an application that uses a
different version of unicode from the default.

This approach allows output from multiple applications with differing
understanding of unicode to coexist on the same screen a little more
sanely.

Note that the default `unicode_version` is set to 9, which means that
emoji presentation selectors are now by-default ignored.  This was
selected to better match the level of support in widely deployed
applications.

I expect to raise that default version in the future.

Also worth noting: there are a number of callers of
`unicode_column_width` in things like overlays and lua helper functions
that pass `None` for the unicode version: these will assume the latest
known-to-wezterm/termwiz version of unicode to be desired. If those
overlays do things with emoji presentation selectors, then there may be
some alignment artifacts. That can be tackled in a follow up commit.

refs: #1231
refs: #997
2021-11-25 09:00:45 -07:00

177 lines
6.1 KiB
Rust

//! This is a really basic ssh client example intended
//! to test the guts of the ssh handling, rather than
//! to be a full fledged replacement for ssh.
use anyhow::Context;
use portable_pty::{Child, MasterPty, PtySize};
use std::io::{Read, Write};
use structopt::StructOpt;
use termwiz::cell::unicode_column_width;
use termwiz::lineedit::*;
use wezterm_ssh::{Config, Session, SessionEvent};
#[derive(Default)]
struct PasswordPromptHost {
history: BasicHistory,
echo: bool,
}
impl LineEditorHost for PasswordPromptHost {
fn history(&mut self) -> &mut dyn History {
&mut self.history
}
fn highlight_line(&self, line: &str, cursor_position: usize) -> (Vec<OutputElement>, usize) {
if self.echo {
(vec![OutputElement::Text(line.to_string())], cursor_position)
} else {
// Rewrite the input so that we can obscure the password
// characters when output to the terminal widget
let placeholder = "🔑";
let grapheme_count = unicode_column_width(line, None);
let mut output = vec![];
for _ in 0..grapheme_count {
output.push(OutputElement::Text(placeholder.to_string()));
}
(
output,
unicode_column_width(placeholder, None) * cursor_position,
)
}
}
}
#[derive(Debug, StructOpt, Default, Clone)]
struct Opt {
#[structopt(long = "user", short = "l")]
pub user: Option<String>,
pub destination: String,
pub cmd: Vec<String>,
}
fn main() {
pretty_env_logger::init();
let opts = Opt::from_args();
let mut config = Config::new();
config.add_default_config_files();
let mut config = config.for_host(&opts.destination);
if let Some(user) = opts.user.as_ref() {
config.insert("user".to_string(), user.to_string());
}
let res = smol::block_on(async move {
let (session, events) = Session::connect(config.clone())?;
while let Ok(event) = events.recv().await {
match event {
SessionEvent::Banner(banner) => {
if let Some(banner) = banner {
log::trace!("{}", banner);
}
}
SessionEvent::HostVerify(verify) => {
eprintln!("{}", verify.message);
let mut terminal = line_editor_terminal()?;
let mut editor = LineEditor::new(&mut terminal);
let mut host = PasswordPromptHost::default();
host.echo = true;
editor.set_prompt("Enter [y/n]> ");
let ok = if let Some(line) = editor.read_line(&mut host)? {
match line.as_ref() {
"y" | "Y" | "yes" | "YES" => true,
"n" | "N" | "no" | "NO" | _ => false,
}
} else {
false
};
verify.answer(ok).await.context("send verify response")?;
}
SessionEvent::Authenticate(auth) => {
if !auth.username.is_empty() {
eprintln!("Authentication for {}", auth.username);
}
if !auth.instructions.is_empty() {
eprintln!("{}", auth.instructions);
}
let mut terminal = line_editor_terminal()?;
let mut editor = LineEditor::new(&mut terminal);
let mut host = PasswordPromptHost::default();
let mut answers = vec![];
for prompt in &auth.prompts {
let mut prompt_lines = prompt.prompt.split('\n').collect::<Vec<_>>();
editor.set_prompt(prompt_lines.pop().unwrap());
host.echo = prompt.echo;
for line in &prompt_lines {
eprintln!("{}", line);
}
if let Some(line) = editor.read_line(&mut host)? {
answers.push(line);
} else {
anyhow::bail!("Authentication was cancelled");
}
}
auth.answer(answers).await?;
}
SessionEvent::Error(err) => {
anyhow::bail!("{}", err);
}
SessionEvent::Authenticated => break,
}
}
let command_line = shell_words::join(&opts.cmd);
let command_line = if command_line.is_empty() {
None
} else {
Some(command_line.as_str())
};
let (pty, mut child) = session
.request_pty("xterm-256color", PtySize::default(), command_line, None)
.await?;
let mut reader = pty.try_clone_reader()?;
let stdout = std::thread::spawn(move || {
let mut buf = [0u8; 8192];
let mut stdout = std::io::stdout();
while let Ok(len) = reader.read(&mut buf) {
if len == 0 {
break;
}
if stdout.write_all(&buf[0..len]).is_err() {
break;
}
}
});
// Need to separate out the writer so that we can drop
// the pty which would otherwise keep the ssh session
// thread alive
let mut writer = pty.try_clone_writer()?;
std::thread::spawn(move || {
let mut buf = [0u8; 8192];
let mut stdin = std::io::stdin();
while let Ok(len) = stdin.read(&mut buf) {
if len == 0 {
break;
}
if writer.write_all(&buf[0..len]).is_err() {
break;
}
}
});
let status = child.wait()?;
let _ = stdout.join();
if !status.success() {
std::process::exit(1);
}
Ok(())
});
if let Err(err) = res {
eprintln!("{:#}", err);
std::process::exit(1);
}
}