use std::num::NonZeroU16;
use unicode_segmentation::UnicodeSegmentation;
use unicode_width::UnicodeWidthStr;
pub const TAB_WIDTH: NonZeroU16 = NonZeroU16::MIN.saturating_add(7);
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct UnicodeVersion {
pub major: u64,
pub minor: u64,
pub update: u64,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub struct UnicodeVersions {
pub width: UnicodeVersion,
pub segmentation: UnicodeVersion,
}
const fn widen(value: u8) -> u64 {
u64::from_le_bytes([value, 0, 0, 0, 0, 0, 0, 0])
}
pub const UNICODE_VERSIONS: UnicodeVersions = UnicodeVersions {
width: UnicodeVersion {
major: widen(unicode_width::UNICODE_VERSION.0),
minor: widen(unicode_width::UNICODE_VERSION.1),
update: widen(unicode_width::UNICODE_VERSION.2),
},
segmentation: UnicodeVersion {
major: unicode_segmentation::UNICODE_VERSION.0,
minor: unicode_segmentation::UNICODE_VERSION.1,
update: unicode_segmentation::UNICODE_VERSION.2,
},
};
#[must_use]
pub fn cluster_width(cluster: &str) -> u16 {
u16::try_from(UnicodeWidthStr::width(cluster)).unwrap_or(u16::MAX)
}
#[must_use]
pub fn text_width(text: &str) -> u16 {
text.graphemes(true)
.map(cluster_width)
.fold(0, u16::saturating_add)
}
#[must_use]
pub fn prefix_len(text: &str, width: u16) -> usize {
let mut used: u16 = 0;
for (offset, cluster) in text.grapheme_indices(true) {
used = used.saturating_add(cluster_width(cluster));
if used > width {
return offset;
}
}
text.len()
}
pub(crate) fn is_break_space(character: char) -> bool {
(character.is_whitespace() && !matches!(character, '\u{a0}' | '\u{2007}' | '\u{202f}'))
|| character == '\u{200b}'
}
pub(crate) fn is_space(cluster: &str) -> bool {
cluster.chars().all(is_break_space)
}