use std::collections::HashMap;
use std::sync::atomic::{AtomicUsize, Ordering};
pub fn u(s: &str) -> String {
s.to_string()
}
pub fn tointiter(s: &[u8]) -> impl Iterator<Item = u8> + '_ {
s.iter().copied()
}
pub fn powerline_decode_error(bytes: &[u8]) -> (String, usize) {
let mut out = String::with_capacity(bytes.len() * 4);
for c in tointiter(bytes) {
out.push_str(&format!("<{:02X}>", c));
}
(out, bytes.len())
}
static LAST_SWE_IDX: AtomicUsize = AtomicUsize::new(0);
pub fn register_strwidth_error<F>(strwidth: F) -> (String, Box<dyn Fn(&str) -> (String, usize)>)
where
F: Fn(&str) -> usize + Send + Sync + 'static,
{
let idx = LAST_SWE_IDX.fetch_add(1, Ordering::SeqCst) + 1;
let handler: Box<dyn Fn(&str) -> (String, usize)> = Box::new(move |slice: &str| {
let w = strwidth(slice);
("?".repeat(w), slice.len())
});
let ename = format!("powerline_encode_strwidth_error_{}", idx);
(ename, handler)
}
pub fn unichr(ch: u32) -> Option<String> {
char::from_u32(ch).map(|c| c.to_string())
}
pub fn powerline_encode_strwidth_error<F>(
strwidth: F,
object: &str,
start: usize,
end: usize,
) -> (String, usize)
where
F: Fn(&str) -> usize,
{
let slice = &object[start..end];
let width = strwidth(slice);
("?".repeat(width), end)
}
pub enum OutUInput<'a> {
Str(&'a str),
Bytes(&'a [u8]),
}
pub fn out_u(input: OutUInput<'_>) -> String {
match input {
OutUInput::Str(s) => out_u_str(s),
OutUInput::Bytes(b) => out_u_bytes(b),
}
}
pub enum StringInput<'a> {
Str(&'a str),
Bytes(&'a [u8]),
}
pub fn string(input: StringInput<'_>) -> String {
match input {
StringInput::Str(s) => string_from_str(s),
StringInput::Bytes(b) => string_from_bytes(b),
}
}
pub fn out_u_str(s: &str) -> String {
s.to_string()
}
pub fn out_u_bytes(s: &[u8]) -> String {
String::from_utf8_lossy(s).into_owned()
}
pub fn safe_unicode_str(s: &str) -> String {
s.to_string()
}
pub fn safe_unicode_bytes(s: &[u8]) -> String {
String::from_utf8_lossy(s).into_owned()
}
pub fn safe_unicode<T: std::fmt::Display>(s: T) -> String {
format!("{}", s)
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FailedUnicode(pub String);
impl FailedUnicode {
pub fn new<T: std::fmt::Display>(s: T) -> Self {
FailedUnicode(format!("{}", s))
}
pub fn as_str(&self) -> &str {
&self.0
}
pub fn into_string(self) -> String {
self.0
}
}
impl std::fmt::Display for FailedUnicode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.0)
}
}
impl From<String> for FailedUnicode {
fn from(s: String) -> Self {
FailedUnicode(s)
}
}
impl From<&str> for FailedUnicode {
fn from(s: &str) -> Self {
FailedUnicode(s.to_string())
}
}
pub fn string_from_str(s: &str) -> String {
s.to_string()
}
pub fn string_from_bytes(s: &[u8]) -> String {
String::from_utf8_lossy(s).into_owned()
}
pub fn surrogate_pair_to_character(high: u32, low: u32) -> u32 {
0x10000 + ((high - 0xD800) << 10) + (low - 0xDC00)
}
pub fn strwidth_ucs_4(width_data: &HashMap<String, usize>, string: &str) -> usize {
let fallback = width_data
.get("N")
.or_else(|| width_data.get("Na"))
.copied()
.unwrap_or(1);
string.chars().map(|_c| fallback).sum()
}
pub fn strwidth_ucs_2(width_data: &HashMap<String, usize>, string: &str) -> usize {
strwidth_ucs_4(width_data, string)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn u_passes_through_str() {
assert_eq!(u("hello"), "hello");
}
#[test]
fn u_handles_utf8() {
assert_eq!(u("héllo →"), "héllo →");
}
#[test]
fn tointiter_yields_byte_ints() {
let v: Vec<u8> = tointiter(b"abc").collect();
assert_eq!(v, vec![b'a', b'b', b'c']);
}
#[test]
fn tointiter_handles_empty() {
let v: Vec<u8> = tointiter(b"").collect();
assert!(v.is_empty());
}
#[test]
fn powerline_decode_error_formats_hex() {
let (s, end) = powerline_decode_error(&[0xff, 0xfe]);
assert_eq!(s, "<FF><FE>");
assert_eq!(end, 2);
}
#[test]
fn powerline_decode_error_handles_empty_range() {
let (s, end) = powerline_decode_error(&[]);
assert!(s.is_empty());
assert_eq!(end, 0);
}
#[test]
fn register_strwidth_error_generates_unique_name() {
let (n1, _) = register_strwidth_error(|s| s.chars().count());
let (n2, _) = register_strwidth_error(|s| s.chars().count());
assert!(n1.starts_with("powerline_encode_strwidth_error_"));
assert!(n2.starts_with("powerline_encode_strwidth_error_"));
assert_ne!(n1, n2);
}
#[test]
fn register_strwidth_error_handler_emits_question_marks() {
let (_, handler) = register_strwidth_error(|s| s.chars().count());
let (out, end) = handler("…");
assert_eq!(out, "?");
assert_eq!(end, "…".len());
}
#[test]
fn register_strwidth_error_handler_respects_width() {
let (_, handler) = register_strwidth_error(|s| s.chars().count() * 2);
let (out, _) = handler("A");
assert_eq!(out, "??");
}
#[test]
fn out_u_str_passes_through() {
assert_eq!(out_u_str("hello"), "hello");
}
#[test]
fn out_u_bytes_decodes_valid_utf8() {
assert_eq!(out_u_bytes(b"hello"), "hello");
}
#[test]
fn out_u_bytes_lossy_on_invalid() {
let bad = &[0xff, b'a'];
let out = out_u_bytes(bad);
assert!(out.contains('\u{FFFD}'));
}
#[test]
fn safe_unicode_str_round_trips() {
assert_eq!(safe_unicode_str("hello"), "hello");
}
#[test]
fn safe_unicode_bytes_handles_valid_utf8() {
assert_eq!(safe_unicode_bytes(b"hello"), "hello");
}
#[test]
fn safe_unicode_bytes_lossy_on_invalid() {
let bad = &[0xff, 0xfe, b'a'];
let result = safe_unicode_bytes(bad);
assert!(result.contains('\u{FFFD}') || result.contains('a'));
}
#[test]
fn safe_unicode_display_works_on_int() {
assert_eq!(safe_unicode(42), "42");
}
#[test]
fn string_from_str_identity() {
assert_eq!(string_from_str("hello"), "hello");
}
#[test]
fn string_from_bytes_decodes_utf8() {
assert_eq!(string_from_bytes(b"hello"), "hello");
}
#[test]
fn string_from_bytes_lossy_on_invalid() {
assert!(string_from_bytes(&[0xff, b'x']).contains('\u{FFFD}'));
}
#[test]
fn surrogate_pair_to_character_round_trips_emoji_range() {
let cp = surrogate_pair_to_character(0xD83D, 0xDE00);
assert_eq!(cp, 0x1F600);
}
#[test]
fn surrogate_pair_to_character_low_surrogate_boundary() {
let cp = surrogate_pair_to_character(0xD800, 0xDC00);
assert_eq!(cp, 0x10000);
}
#[test]
fn failed_unicode_display_returns_inner_string() {
let fu = FailedUnicode::new("No window 5");
assert_eq!(fu.to_string(), "No window 5");
}
#[test]
fn failed_unicode_eq_compares_by_value() {
let a = FailedUnicode::from("err");
let b = FailedUnicode::from(String::from("err"));
assert_eq!(a, b);
}
#[test]
fn failed_unicode_into_string_yields_inner() {
let fu = FailedUnicode::new("oops");
assert_eq!(fu.into_string(), "oops");
}
#[test]
fn strwidth_ucs_4_sums_with_default_table() {
let mut width_data = HashMap::new();
width_data.insert("N".to_string(), 1);
width_data.insert("Na".to_string(), 1);
assert_eq!(strwidth_ucs_4(&width_data, "hello"), 5);
assert_eq!(strwidth_ucs_4(&width_data, ""), 0);
}
#[test]
fn strwidth_ucs_4_uses_table_value() {
let mut width_data = HashMap::new();
width_data.insert("N".to_string(), 2);
width_data.insert("Na".to_string(), 2);
assert_eq!(strwidth_ucs_4(&width_data, "hi"), 4);
}
#[test]
fn strwidth_ucs_4_empty_table_falls_back_to_one() {
let width_data = HashMap::new();
assert_eq!(strwidth_ucs_4(&width_data, "abc"), 3);
}
#[test]
fn strwidth_ucs_2_matches_ucs_4_for_basic_strs() {
let mut width_data = HashMap::new();
width_data.insert("N".to_string(), 1);
width_data.insert("Na".to_string(), 1);
assert_eq!(
strwidth_ucs_2(&width_data, "hello"),
strwidth_ucs_4(&width_data, "hello")
);
}
#[test]
fn unichr_returns_basic_ascii() {
assert_eq!(unichr(b'A' as u32), Some("A".to_string()));
assert_eq!(unichr(0x20), Some(" ".to_string()));
}
#[test]
fn unichr_returns_high_codepoint_as_single_char() {
let r = unichr(0x1F600).unwrap();
assert_eq!(r.chars().count(), 1);
assert_eq!(r, "\u{1F600}");
}
#[test]
fn unichr_returns_none_for_surrogate_codepoint() {
assert_eq!(unichr(0xD800), None);
assert_eq!(unichr(0xDFFF), None);
}
#[test]
fn powerline_encode_strwidth_error_replaces_with_question_marks() {
let object = "abc";
let strwidth = |s: &str| s.chars().count();
let (replacement, new_end) = powerline_encode_strwidth_error(strwidth, object, 0, 3);
assert_eq!(replacement, "???");
assert_eq!(new_end, 3);
}
#[test]
fn out_u_dispatches_str_to_str_branch() {
let r = out_u(OutUInput::Str("hello"));
assert_eq!(r, "hello");
}
#[test]
fn out_u_dispatches_bytes_to_bytes_branch() {
let r = out_u(OutUInput::Bytes(b"world"));
assert_eq!(r, "world");
}
#[test]
fn string_dispatches_str_to_identity() {
let r = string(StringInput::Str("hello"));
assert_eq!(r, "hello");
}
#[test]
fn string_dispatches_bytes_to_utf8_decode() {
let r = string(StringInput::Bytes(b"hello"));
assert_eq!(r, "hello");
}
}