#![cfg(any(feature = "markdown", feature = "html"))]
use std::cmp::Ordering;
use std::fmt::{self, Write as _};
pub const MENTION_URL_PREFIX: &str = "tg://user?id=";
pub fn telegram_string_len(string: &str) -> i32 {
string.encode_utf16().count() as i32
}
macro_rules! update_entity_len {
( $ty:ident($end_offset:expr) in $vector:expr ) => {
let mut remove = false;
let end_offset = $end_offset;
let pos = $vector.iter_mut().rposition(|e| match e {
tl::enums::MessageEntity::$ty(e) => {
e.length = end_offset - e.offset;
remove = e.length == 0;
true
}
_ => false,
});
if remove {
$vector.remove(pos.unwrap());
}
};
}
pub(crate) use update_entity_len;
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
enum Edge {
After,
Before,
}
#[derive(Debug, PartialEq, Eq)]
pub struct Position {
offset: i32,
edge: Edge,
index: usize,
part: u8,
}
impl Ord for Position {
fn cmp(&self, other: &Self) -> Ordering {
self.offset
.cmp(&other.offset)
.then_with(|| self.edge.cmp(&other.edge))
.then_with(|| match self.edge {
Edge::Before => self.index.cmp(&other.index),
Edge::After => other.index.cmp(&self.index),
})
.then_with(|| self.part.cmp(&other.part))
}
}
impl PartialOrd for Position {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
#[derive(Debug)]
pub enum Segment<'a> {
Fixed(&'static str),
String(&'a str),
Number(i64),
}
impl<'a> Segment<'a> {
fn len(&self) -> usize {
match self {
Self::Fixed(s) => s.len(),
Self::String(s) => s.len(),
Self::Number(n) => {
let minus_sign = if *n < 0 { 1 } else { 0 };
let digits = n.abs().ilog10() as usize + 1;
minus_sign + digits
}
}
}
}
impl<'a> fmt::Display for Segment<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Fixed(s) => f.write_str(s),
Self::String(s) => f.write_str(s),
Self::Number(s) => write!(f, "{}", s),
}
}
}
#[inline(always)]
pub fn before(index: usize, part: u8, offset: i32) -> Position {
Position {
index,
offset,
edge: Edge::Before,
part,
}
}
#[inline(always)]
pub fn after(index: usize, part: u8, offset: i32) -> Position {
Position {
index,
offset,
edge: Edge::After,
part,
}
}
pub fn inject_into_message<'a, I>(message: &str, insertions: I) -> String
where
I: IntoIterator<Item = (Position, Segment<'a>)>,
{
let mut insertions = insertions.into_iter().collect::<Vec<_>>();
let mut result = String::with_capacity(
message.len() + insertions.iter().map(|(_, what)| what.len()).sum::<usize>(),
);
insertions.sort_unstable_by(|(a, _), (b, _)| b.cmp(a));
let mut char_buffer = [0u8; 4]; let mut prev_point = None;
for (index, point) in message.encode_utf16().enumerate() {
loop {
match insertions.last() {
Some((at, what)) if at.offset as usize == index => {
write!(result, "{}", what).unwrap();
insertions.pop();
}
_ => break,
}
}
let c = if let Some(previous) = prev_point.take() {
char::decode_utf16([previous, point])
.next()
.unwrap()
.unwrap()
} else {
match char::decode_utf16([point]).next().unwrap() {
Ok(c) => c,
Err(unpaired) => {
prev_point = Some(unpaired.unpaired_surrogate());
continue;
}
}
};
result.push_str(c.encode_utf8(&mut char_buffer));
}
while let Some((_, what)) = insertions.pop() {
write!(result, "{}", what).unwrap();
}
result
}