url_preview/
utils.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
use unicode_width::UnicodeWidthChar;

use url::{ParseError, Url};

/// Safely truncate a string, ensuring it is not truncated in the middle of multi-byte characters
///
/// This function will:
/// 1. Correctly handle Unicode characters (including Chinese, emoji, etc.)
/// 2. Add ellipsis when maximum length is reached
/// 3. Ensure the output string's display width does not exceed the specified length
pub fn truncate_str(s: &str, max_width: usize) -> String {
    use unicode_width::UnicodeWidthStr;

    if s.width() <= max_width {
        return s.to_string();
    }

    let mut result = String::new();
    let mut current_width = 0;

    for c in s.chars() {
        let char_width = c.width().unwrap_or(1);

        if current_width + char_width + 3 > max_width {
            break;
        }

        result.push(c);
        current_width += char_width;
    }

    result.push_str("...");
    result
}

pub fn pickup_host_from_url(url: &str) -> Result<String, ParseError> {
    let parsed_url = Url::parse(url)?;
    parsed_url.host_str().map(String::from);
    let scheme = parsed_url.scheme();
    let host = parsed_url.host_str().ok_or(url::ParseError::EmptyHost)?;

    let port = parsed_url
        .port()
        .map(|x| format!(":{}", x))
        .or_else(|| Some("".to_string()))
        .unwrap();

    Ok(format!("{}://{}{}/", scheme, host, port))
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_truncate_str() {
        assert_eq!(truncate_str("Hello, world!", 10), "Hello, ...");
        assert_eq!(truncate_str("你好,世界!", 8), "你好...");
        assert_eq!(truncate_str("Hello 你好!", 10), "Hello ...");
        assert_eq!(truncate_str("Hi!", 10), "Hi!");
    }
}