lento 0.1.1

Cross platform ui framework
use std::ops::{Bound, RangeBounds};

pub trait StringUtils {
    fn substring(&self, start: usize, len: usize) -> &str;
    fn slice(&self, range: impl RangeBounds<usize>) -> &str;
    fn byte_index(&self, char_index: usize) -> usize;
    fn chars_count(&self) -> usize;
    fn trim_line_endings(&self) -> &str;
}

impl StringUtils for str {

    fn chars_count(&self) -> usize {
        self.chars().count()
    }
    fn byte_index(&self, char_offset: usize) -> usize {
        return if char_offset == 0 {
            0
        } else {
            self.substring(0, char_offset).len()
        }
    }

    fn substring(&self, start: usize, len: usize) -> &str {
        let mut char_pos = 0;
        let mut byte_start = 0;
        let mut it = self.chars();
        loop {
            if char_pos == start { break; }
            if let Some(c) = it.next() {
                char_pos += 1;
                byte_start += c.len_utf8();
            } else { break; }
        }
        char_pos = 0;
        let mut byte_end = byte_start;
        loop {
            if char_pos == len { break; }
            if let Some(c) = it.next() {
                char_pos += 1;
                byte_end += c.len_utf8();
            } else { break; }
        }
        &self[byte_start..byte_end]
    }
    fn slice(&self, range: impl RangeBounds<usize>) -> &str {
        let start = match range.start_bound() {
            Bound::Included(bound) | Bound::Excluded(bound) => *bound,
            Bound::Unbounded => 0,
        };
        let len = match range.end_bound() {
            Bound::Included(bound) => *bound + 1,
            Bound::Excluded(bound) => *bound,
            Bound::Unbounded => self.len(),
        } - start;
        self.substring(start, len)
    }

    fn trim_line_endings(&self) -> &str {
        self.trim_end_matches(|e| e == '\r' || e == '\n')
    }

}