upgate-presentation 0.1.0

Keep globally installed developer tools up to date
Documentation
use ratatui::text::Span;
use unicode_width::{UnicodeWidthChar, UnicodeWidthStr};

use crate::outcome::changed_version_segment_index;
use crate::tui::theme::TuiTheme;

const ELLIPSIS: char = '';

pub(super) fn version_diff_spans(
    from: &str,
    to: &str,
    base_style: ratatui::style::Style,
    theme: &TuiTheme,
    selected: bool,
) -> Vec<Span<'static>> {
    let changed_from = changed_version_segment_index(from, to);
    let changed_style = base_style.patch(theme.version_changed_for(selected));
    let to_core = to.strip_prefix('v').unwrap_or(to);

    let mut spans = Vec::new();
    if to.starts_with('v') {
        spans.push(Span::styled("v", base_style));
    }

    for (idx, part) in to_core.split('.').enumerate() {
        if idx > 0 {
            spans.push(Span::styled(".", base_style));
        }
        let style = if changed_from.is_some_and(|first| idx >= first) {
            changed_style
        } else {
            base_style
        };
        spans.push(Span::styled(part.to_owned(), style));
    }

    spans
}

pub(super) fn truncate_with_ellipsis(text: &str, max_width: usize) -> String {
    if max_width == 0 {
        return String::new();
    }

    if UnicodeWidthStr::width(text) <= max_width {
        return text.to_owned();
    }

    if max_width == 1 {
        return ELLIPSIS.to_string();
    }

    let mut clipped = String::new();
    let mut used = 0usize;
    let keep_width = max_width - 1;

    for ch in text.chars() {
        let ch_width = UnicodeWidthChar::width(ch).unwrap_or(0);
        if used + ch_width > keep_width {
            break;
        }

        clipped.push(ch);
        used += ch_width;
    }

    clipped.push(ELLIPSIS);
    clipped
}