zellij_utils/
shared.rs

1//! Some general utility functions.
2
3use std::net::{IpAddr, Ipv4Addr};
4use std::{iter, str::from_utf8};
5
6use crate::data::{Palette, PaletteColor, PaletteSource, ThemeHue};
7use crate::envs::get_session_name;
8use crate::input::options::Options;
9use colorsys::{Ansi256, Rgb};
10use strip_ansi_escapes::strip;
11use unicode_width::UnicodeWidthStr;
12
13#[cfg(unix)]
14pub use unix_only::*;
15
16#[cfg(unix)]
17mod unix_only {
18    use std::os::unix::fs::PermissionsExt;
19    use std::path::Path;
20    use std::{fs, io};
21
22    pub fn set_permissions(path: &Path, mode: u32) -> io::Result<()> {
23        let mut permissions = fs::metadata(path)?.permissions();
24        permissions.set_mode(mode);
25        fs::set_permissions(path, permissions)
26    }
27}
28
29#[cfg(not(unix))]
30pub fn set_permissions(_path: &std::path::Path, _mode: u32) -> std::io::Result<()> {
31    Ok(())
32}
33
34pub fn ansi_len(s: &str) -> usize {
35    from_utf8(&strip(s).unwrap()).unwrap().width()
36}
37
38pub fn clean_string_from_control_and_linebreak(input: &str) -> String {
39    input
40        .chars()
41        .filter(|c| {
42            !c.is_control() &&
43            *c != '\n' &&      // line feed
44            *c != '\r' &&      // carriage return
45            *c != '\u{2028}' && // line separator
46            *c != '\u{2029}' // paragraph separator
47        })
48        .collect()
49}
50
51pub fn adjust_to_size(s: &str, rows: usize, columns: usize) -> String {
52    s.lines()
53        .map(|l| {
54            let actual_len = ansi_len(l);
55            if actual_len > columns {
56                let mut line = String::from(l);
57                line.truncate(columns);
58                line
59            } else {
60                [l, &str::repeat(" ", columns - ansi_len(l))].concat()
61            }
62        })
63        .chain(iter::repeat(str::repeat(" ", columns)))
64        .take(rows)
65        .collect::<Vec<_>>()
66        .join("\n\r")
67}
68
69pub fn make_terminal_title(pane_title: &str) -> String {
70    format!(
71        "\u{1b}]0;{}{}\u{07}",
72        get_session_name()
73            .map(|n| if pane_title.is_empty() {
74                format!("{}", n)
75            } else {
76                format!("{} | ", n)
77            })
78            .unwrap_or_default(),
79        pane_title
80    )
81}
82
83// Colors
84pub mod colors {
85    pub const WHITE: u8 = 255;
86    pub const GREEN: u8 = 154;
87    pub const GRAY: u8 = 238;
88    pub const BRIGHT_GRAY: u8 = 245;
89    pub const RED: u8 = 124;
90    pub const ORANGE: u8 = 166;
91    pub const BLACK: u8 = 16;
92    pub const MAGENTA: u8 = 201;
93    pub const CYAN: u8 = 51;
94    pub const YELLOW: u8 = 226;
95    pub const BLUE: u8 = 45;
96    pub const PURPLE: u8 = 99;
97    pub const GOLD: u8 = 136;
98    pub const SILVER: u8 = 245;
99    pub const PINK: u8 = 207;
100    pub const BROWN: u8 = 215;
101}
102
103pub fn _hex_to_rgb(hex: &str) -> (u8, u8, u8) {
104    Rgb::from_hex_str(hex)
105        .expect("The passed argument must be a valid hex color")
106        .into()
107}
108
109pub fn eightbit_to_rgb(c: u8) -> (u8, u8, u8) {
110    Ansi256::new(c).as_rgb().into()
111}
112
113pub fn default_palette() -> Palette {
114    Palette {
115        source: PaletteSource::Default,
116        theme_hue: ThemeHue::Dark,
117        fg: PaletteColor::EightBit(colors::BRIGHT_GRAY),
118        bg: PaletteColor::EightBit(colors::GRAY),
119        black: PaletteColor::EightBit(colors::BLACK),
120        red: PaletteColor::EightBit(colors::RED),
121        green: PaletteColor::EightBit(colors::GREEN),
122        yellow: PaletteColor::EightBit(colors::YELLOW),
123        blue: PaletteColor::EightBit(colors::BLUE),
124        magenta: PaletteColor::EightBit(colors::MAGENTA),
125        cyan: PaletteColor::EightBit(colors::CYAN),
126        white: PaletteColor::EightBit(colors::WHITE),
127        orange: PaletteColor::EightBit(colors::ORANGE),
128        gray: PaletteColor::EightBit(colors::GRAY),
129        purple: PaletteColor::EightBit(colors::PURPLE),
130        gold: PaletteColor::EightBit(colors::GOLD),
131        silver: PaletteColor::EightBit(colors::SILVER),
132        pink: PaletteColor::EightBit(colors::PINK),
133        brown: PaletteColor::EightBit(colors::BROWN),
134    }
135}
136
137// Dark magic
138pub fn detect_theme_hue(bg: PaletteColor) -> ThemeHue {
139    match bg {
140        PaletteColor::Rgb((r, g, b)) => {
141            // HSP, P stands for perceived brightness
142            let hsp: f64 = (0.299 * (r as f64 * r as f64)
143                + 0.587 * (g as f64 * g as f64)
144                + 0.114 * (b as f64 * b as f64))
145                .sqrt();
146            match hsp > 127.5 {
147                true => ThemeHue::Light,
148                false => ThemeHue::Dark,
149            }
150        },
151        _ => ThemeHue::Dark,
152    }
153}
154
155// (this was shamelessly copied from alacritty)
156//
157// This returns the current terminal version as a unique number based on the
158// semver version. The different versions are padded to ensure that a higher semver version will
159// always report a higher version number.
160pub fn version_number(mut version: &str) -> usize {
161    if let Some(separator) = version.rfind('-') {
162        version = &version[..separator];
163    }
164
165    let mut version_number = 0;
166
167    let semver_versions = version.split('.');
168    for (i, semver_version) in semver_versions.rev().enumerate() {
169        let semver_number = semver_version.parse::<usize>().unwrap_or(0);
170        version_number += usize::pow(100, i as u32) * semver_number;
171    }
172
173    version_number
174}
175
176pub fn web_server_base_url(
177    web_server_ip: IpAddr,
178    web_server_port: u16,
179    has_certificate: bool,
180    enforce_https_for_localhost: bool,
181) -> String {
182    let is_loopback = match web_server_ip {
183        IpAddr::V4(ipv4) => ipv4.is_loopback(),
184        IpAddr::V6(ipv6) => ipv6.is_loopback(),
185    };
186
187    let url_prefix = if is_loopback && !enforce_https_for_localhost && !has_certificate {
188        "http"
189    } else {
190        "https"
191    };
192    format!("{}://{}:{}", url_prefix, web_server_ip, web_server_port)
193}
194
195pub fn web_server_base_url_from_config(config_options: Options) -> String {
196    let web_server_ip = config_options
197        .web_server_ip
198        .unwrap_or_else(|| IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)));
199    let web_server_port = config_options.web_server_port.unwrap_or_else(|| 8082);
200    let has_certificate =
201        config_options.web_server_cert.is_some() && config_options.web_server_key.is_some();
202    let enforce_https_for_localhost = config_options.enforce_https_for_localhost.unwrap_or(false);
203    web_server_base_url(
204        web_server_ip,
205        web_server_port,
206        has_certificate,
207        enforce_https_for_localhost,
208    )
209}