use {
crate::*,
anyhow::*,
std::io::Write,
termimad::StrFit,
};
pub const CSI_RESET: &str = "\u{1b}[0m\u{1b}[0m";
pub const CSI_BOLD: &str = "\u{1b}[1m";
pub const CSI_BOLD_RED: &str = "\u{1b}[1m\u{1b}[38;5;9m";
pub const CSI_BOLD_ORANGE: &str = "\u{1b}[1m\u{1b}[38;5;208m";
pub const CSI_BOLD_YELLOW: &str = "\u{1b}[1m\u{1b}[33m";
pub const CSI_BOLD_BLUE: &str = "\u{1b}[1m\u{1b}[38;5;12m";
pub const CSI_ITALIC: &str = "\u{1b}[3m";
static TAB_REPLACEMENT: &str = " ";
#[derive(Debug, Default, Clone)]
pub struct TString {
pub csi: String,
pub raw: String,
}
impl TString {
pub fn badge(con: &str, fg: u8, bg: u8) -> Self {
Self {
csi: format!("\u{1b}[1m\u{1b}[38;5;{}m\u{1b}[48;5;{}m", fg, bg),
raw: format!(" {} ", con),
}
}
pub fn num_badge(num: usize, cat: &str, fg: u8, bg: u8) -> Self {
let raw = if num == 1 {
format!(" 1 {} ", cat)
} else {
format!(" {} {}s ", num, cat)
};
Self::badge(&raw, fg, bg)
}
pub fn push_csi(&mut self, params: &[i64], action: char) {
self.csi.push('\u{1b}');
self.csi.push('[');
for (idx, p) in params.iter().enumerate() {
self.csi.push_str(&format!("{}", p));
if idx < params.len() - 1 {
self.csi.push(';');
}
}
self.csi.push(action);
}
pub fn draw(&self, w: &mut W) -> Result<()> {
if self.csi.is_empty() {
write!(w, "{}", &self.raw)?;
} else {
write!(w, "{}{}{}", &self.csi, &self.raw, CSI_RESET,)?;
}
Ok(())
}
pub fn draw_in(&self, w: &mut W, cols_max: usize) -> Result<usize> {
let fit = StrFit::make_cow(&self.raw, cols_max);
if self.csi.is_empty() {
write!(w, "{}", &fit.0)?;
} else {
write!(w, "{}{}{}", &self.csi, &fit.0, CSI_RESET,)?;
}
Ok(fit.1)
}
pub fn starts_with(&self, csi: &str, raw: &str) -> bool {
self.csi == csi && self.raw.starts_with(raw)
}
pub fn split_off(&mut self, at: usize) -> Self {
Self {
csi: self.csi.clone(),
raw: self.raw.split_off(at),
}
}
}
#[derive(Debug, Default, Clone)]
pub struct TLine {
pub strings: Vec<TString>,
}
impl TLine {
pub fn from_tty(tty: &str) -> Self {
let tty_str: String;
let tty = if tty.contains('\t') {
tty_str = tty.replace('\t', TAB_REPLACEMENT);
&tty_str
} else {
tty
};
let mut parser = vte::Parser::new();
let mut builder = TLineBuilder::default();
for byte in tty.bytes() {
parser.advance(&mut builder, byte);
}
builder.into_tline()
}
pub fn from_raw(raw: String) -> Self {
Self {
strings: vec![TString {
csi: " ".to_string(),
raw,
}],
}
}
pub fn bold(raw: String) -> Self {
Self {
strings: vec![TString {
csi: CSI_BOLD.to_string(),
raw,
}],
}
}
pub fn italic(raw: String) -> Self {
Self {
strings: vec![TString {
csi: CSI_ITALIC.to_string(),
raw,
}],
}
}
pub fn failed(key: &str) -> Self {
Self {
strings: vec![
TString {
csi: CSI_BOLD_ORANGE.to_string(),
raw: "failed".to_string(),
},
TString {
csi: CSI_BOLD.to_string(),
raw: format!(": {}", key),
},
],
}
}
pub fn add_badge(&mut self, badge: TString) {
self.strings.push(badge);
self.strings.push(TString {
csi: "".to_string(),
raw: " ".to_string(),
});
}
pub fn draw(&self, w: &mut W) -> Result<()> {
for ts in &self.strings {
ts.draw(w)?;
}
Ok(())
}
pub fn draw_in(&self, w: &mut W, cols_max: usize) -> Result<usize> {
let mut cols = 0;
for ts in &self.strings {
if cols >= cols_max {
break;
}
cols += ts.draw_in(w, cols_max - cols)?;
}
Ok(cols)
}
pub fn is_blank(&self) -> bool {
return self.strings.iter().all(|s| s.raw.trim().is_empty());
}
pub fn if_unstyled(&self) -> Option<&str> {
if self.strings.len() == 1 {
self.strings
.get(0)
.filter(|s| s.csi.is_empty())
.map(|s| s.raw.as_str())
} else {
None
}
}
}
#[derive(Debug, Default)]
pub struct TLineBuilder {
cur: Option<TString>,
strings: Vec<TString>,
}
impl TLineBuilder {
pub fn into_tline(mut self) -> TLine {
if let Some(cur) = self.cur {
self.strings.push(cur);
}
TLine {
strings: self.strings,
}
}
}
impl vte::Perform for TLineBuilder {
fn print(&mut self, c: char) {
self.cur.get_or_insert_with(TString::default).raw.push(c);
}
fn csi_dispatch(&mut self, params: &[i64], _intermediates: &[u8], _ignore: bool, action: char) {
if *params == [0] {
if let Some(cur) = self.cur.take() {
self.strings.push(cur);
}
return;
}
if let Some(cur) = self.cur.as_mut() {
if cur.raw.is_empty() {
cur.push_csi(params, action);
return;
}
}
if let Some(cur) = self.cur.take() {
self.strings.push(cur);
}
let mut cur = TString::default();
cur.push_csi(params, action);
self.cur = Some(cur);
}
fn execute(&mut self, _byte: u8) {}
fn hook(&mut self, _params: &[i64], _intermediates: &[u8], _ignore: bool, _action: char) {}
fn put(&mut self, _byte: u8) {}
fn unhook(&mut self) {}
fn osc_dispatch(&mut self, _params: &[&[u8]], _bell_terminated: bool) {}
fn esc_dispatch(&mut self, _intermediates: &[u8], _ignore: bool, _byte: u8) {}
}