use std::collections::HashMap;
use std::env;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TerminalType {
ITerm2,
Kitty,
Alacritty,
WezTerm,
WindowsTerminal,
VSCode,
AppleTerminal,
Unknown,
}
impl TerminalType {
pub fn detect() -> Self {
if env::var("TERM_PROGRAM").as_deref() == Ok("iTerm.app") {
return Self::ITerm2;
}
if env::var("TERM_PROGRAM").as_deref() == Ok("Apple_Terminal") {
return Self::AppleTerminal;
}
if env::var("TERM_PROGRAM").as_deref() == Ok("vscode") {
return Self::VSCode;
}
if env::var("KITTY_WINDOW_ID").is_ok() {
return Self::Kitty;
}
if env::var("ALACRITTY_SOCKET").is_ok() || env::var("ALACRITTY_LOG").is_ok() {
return Self::Alacritty;
}
if env::var("WEZTERM_PANE").is_ok() {
return Self::WezTerm;
}
if env::var("WT_SESSION").is_ok() {
return Self::WindowsTerminal;
}
Self::Unknown
}
pub fn emoji_width(&self) -> u8 {
match self {
Self::ITerm2
| Self::Kitty
| Self::Alacritty
| Self::WezTerm
| Self::WindowsTerminal
| Self::VSCode => 2,
Self::AppleTerminal => 2,
Self::Unknown => 2,
}
}
pub fn cjk_width(&self) -> u8 {
2
}
pub fn nerd_font_width(&self) -> u8 {
match self {
Self::Kitty | Self::WezTerm | Self::Alacritty | Self::ITerm2 => 1,
_ => 1,
}
}
}
pub struct CharWidthTable {
pub cjk: u8,
pub emoji: u8,
pub nerd_font: u8,
pub terminal: TerminalType,
overrides: HashMap<char, u8>,
}
impl CharWidthTable {
pub fn new() -> Self {
Self {
cjk: 2,
emoji: 2,
nerd_font: 1,
terminal: TerminalType::Unknown,
overrides: HashMap::new(),
}
}
pub fn detect() -> Self {
let terminal = TerminalType::detect();
Self {
cjk: terminal.cjk_width(),
emoji: terminal.emoji_width(),
nerd_font: terminal.nerd_font_width(),
terminal,
overrides: HashMap::new(),
}
}
pub fn for_terminal(terminal: TerminalType) -> Self {
Self {
cjk: terminal.cjk_width(),
emoji: terminal.emoji_width(),
nerd_font: terminal.nerd_font_width(),
terminal,
overrides: HashMap::new(),
}
}
pub fn width(&self, ch: char) -> u8 {
if let Some(&w) = self.overrides.get(&ch) {
return w;
}
if unic_emoji_char::is_emoji(ch) {
return self.emoji;
}
unicode_width::UnicodeWidthChar::width(ch)
.map(|w| w as u8)
.unwrap_or(1)
}
pub fn set_override(&mut self, ch: char, width: u8) {
self.overrides.insert(ch, width);
}
pub fn with_cjk(mut self, width: u8) -> Self {
self.cjk = width;
self
}
pub fn with_emoji(mut self, width: u8) -> Self {
self.emoji = width;
self
}
pub fn with_nerd_font(mut self, width: u8) -> Self {
self.nerd_font = width;
self
}
}
impl Default for CharWidthTable {
fn default() -> Self {
Self::new()
}
}
pub fn char_width(ch: char) -> u8 {
crate::utils::unicode::char_width(ch) as u8
}
#[cfg(test)] pub fn str_width(s: &str) -> usize {
unicode_width::UnicodeWidthStr::width(s)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_ascii_width() {
assert_eq!(char_width('a'), 1);
assert_eq!(char_width('Z'), 1);
assert_eq!(char_width(' '), 1);
assert_eq!(char_width('!'), 1);
}
#[test]
fn test_cjk_width() {
assert_eq!(char_width('한'), 2);
assert_eq!(char_width('글'), 2);
assert_eq!(char_width('日'), 2);
assert_eq!(char_width('本'), 2);
assert_eq!(char_width('中'), 2);
}
#[test]
fn test_str_width() {
assert_eq!(str_width("hello"), 5);
assert_eq!(str_width("한글"), 4);
assert_eq!(str_width("hello한글"), 9);
}
#[test]
fn test_width_table_override() {
let mut table = CharWidthTable::new();
table.set_override('X', 3);
assert_eq!(table.width('X'), 3);
assert_eq!(table.width('Y'), 1); }
#[test]
fn test_emoji_width() {
let table = CharWidthTable::new();
assert_eq!(table.width('😀'), 2);
assert_eq!(table.width('🔥'), 2);
}
#[test]
fn test_width_table_builder() {
let table = CharWidthTable::new()
.with_cjk(2)
.with_emoji(2)
.with_nerd_font(1);
assert_eq!(table.cjk, 2);
assert_eq!(table.emoji, 2);
assert_eq!(table.nerd_font, 1);
}
}