//! Some general utility functions. use std::net::{IpAddr, Ipv4Addr}; use std::{iter, str::from_utf8}; use crate::data::{Palette, PaletteColor, PaletteSource, ThemeHue}; use crate::envs::get_session_name; use crate::input::options::Options; use colorsys::{Ansi256, Rgb}; use strip_ansi_escapes::strip; use unicode_width::UnicodeWidthStr; #[cfg(unix)] pub use unix_only::*; #[cfg(unix)] mod unix_only { use std::os::unix::fs::PermissionsExt; use std::path::Path; use std::{fs, io}; pub fn set_permissions(path: &Path, mode: u32) -> io::Result<()> { let mut permissions = fs::metadata(path)?.permissions(); permissions.set_mode(mode); fs::set_permissions(path, permissions) } } #[cfg(not(unix))] pub fn set_permissions(_path: &std::path::Path, _mode: u32) -> std::io::Result<()> { Ok(()) } pub fn ansi_len(s: &str) -> usize { from_utf8(&strip(s).unwrap()).unwrap().width() } pub fn clean_string_from_control_and_linebreak(input: &str) -> String { input .chars() .filter(|c| { !c.is_control() && *c != '\n' && // line feed *c != '\r' && // carriage return *c != '\u{2028}' && // line separator *c != '\u{2029}' // paragraph separator }) .collect() } pub fn adjust_to_size(s: &str, rows: usize, columns: usize) -> String { s.lines() .map(|l| { let actual_len = ansi_len(l); if actual_len > columns { let mut line = String::from(l); line.truncate(columns); line } else { [l, &str::repeat(" ", columns - ansi_len(l))].concat() } }) .chain(iter::repeat(str::repeat(" ", columns))) .take(rows) .collect::>() .join("\n\r") } pub fn make_terminal_title(pane_title: &str) -> String { // if we receive a title, we display it, otherwise we display the session name if pane_title.is_empty() { format!( "\u{1b}]0;Zellij {}\u{07}", get_session_name() .map(|n| format!("({}) ", n)) .unwrap_or_default() ) } else { format!("\u{1b}]0;{}\u{07}", pane_title,) } } // Colors pub mod colors { pub const WHITE: u8 = 255; pub const GREEN: u8 = 154; pub const GRAY: u8 = 238; pub const BRIGHT_GRAY: u8 = 245; pub const RED: u8 = 124; pub const ORANGE: u8 = 166; pub const BLACK: u8 = 16; pub const MAGENTA: u8 = 201; pub const CYAN: u8 = 51; pub const YELLOW: u8 = 226; pub const BLUE: u8 = 45; pub const PURPLE: u8 = 99; pub const GOLD: u8 = 136; pub const SILVER: u8 = 245; pub const PINK: u8 = 207; pub const BROWN: u8 = 215; } pub fn _hex_to_rgb(hex: &str) -> (u8, u8, u8) { Rgb::from_hex_str(hex) .expect("The passed argument must be a valid hex color") .into() } pub fn eightbit_to_rgb(c: u8) -> (u8, u8, u8) { Ansi256::new(c).as_rgb().into() } pub fn default_palette() -> Palette { Palette { source: PaletteSource::Default, theme_hue: ThemeHue::Dark, fg: PaletteColor::EightBit(colors::BRIGHT_GRAY), bg: PaletteColor::EightBit(colors::GRAY), black: PaletteColor::EightBit(colors::BLACK), red: PaletteColor::EightBit(colors::RED), green: PaletteColor::EightBit(colors::GREEN), yellow: PaletteColor::EightBit(colors::YELLOW), blue: PaletteColor::EightBit(colors::BLUE), magenta: PaletteColor::EightBit(colors::MAGENTA), cyan: PaletteColor::EightBit(colors::CYAN), white: PaletteColor::EightBit(colors::WHITE), orange: PaletteColor::EightBit(colors::ORANGE), gray: PaletteColor::EightBit(colors::GRAY), purple: PaletteColor::EightBit(colors::PURPLE), gold: PaletteColor::EightBit(colors::GOLD), silver: PaletteColor::EightBit(colors::SILVER), pink: PaletteColor::EightBit(colors::PINK), brown: PaletteColor::EightBit(colors::BROWN), } } // Dark magic pub fn detect_theme_hue(bg: PaletteColor) -> ThemeHue { match bg { PaletteColor::Rgb((r, g, b)) => { // HSP, P stands for perceived brightness let hsp: f64 = (0.299 * (r as f64 * r as f64) + 0.587 * (g as f64 * g as f64) + 0.114 * (b as f64 * b as f64)) .sqrt(); match hsp > 127.5 { true => ThemeHue::Light, false => ThemeHue::Dark, } }, _ => ThemeHue::Dark, } } // (this was shamelessly copied from alacritty) // // This returns the current terminal version as a unique number based on the // semver version. The different versions are padded to ensure that a higher semver version will // always report a higher version number. pub fn version_number(mut version: &str) -> usize { if let Some(separator) = version.rfind('-') { version = &version[..separator]; } let mut version_number = 0; let semver_versions = version.split('.'); for (i, semver_version) in semver_versions.rev().enumerate() { let semver_number = semver_version.parse::().unwrap_or(0); version_number += usize::pow(100, i as u32) * semver_number; } version_number } pub fn web_server_base_url( web_server_ip: IpAddr, web_server_port: u16, has_certificate: bool, enforce_https_for_localhost: bool, ) -> String { let is_loopback = match web_server_ip { IpAddr::V4(ipv4) => ipv4.is_loopback(), IpAddr::V6(ipv6) => ipv6.is_loopback(), }; let url_prefix = if is_loopback && !enforce_https_for_localhost && !has_certificate { "http" } else { "https" }; format!("{}://{}:{}", url_prefix, web_server_ip, web_server_port) } pub fn web_server_base_url_from_config(config_options: Options) -> String { let web_server_ip = config_options .web_server_ip .unwrap_or_else(|| IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1))); let web_server_port = config_options.web_server_port.unwrap_or_else(|| 8082); let has_certificate = config_options.web_server_cert.is_some() && config_options.web_server_key.is_some(); let enforce_https_for_localhost = config_options.enforce_https_for_localhost.unwrap_or(false); web_server_base_url( web_server_ip, web_server_port, has_certificate, enforce_https_for_localhost, ) }