vtcode 0.99.1

A Rust-based terminal coding agent with modular architecture supporting multiple LLM providers
use anstyle::Style as AnsiStyle;
use anyhow::Result;
use vtcode_core::utils::ansi::{AnsiRenderer, MessageStyle};

#[derive(Clone)]
pub(crate) struct PanelContentLine {
    pub(crate) rendered: String,
    pub(crate) style: MessageStyle,
    pub(crate) override_style: Option<AnsiStyle>,
}

impl PanelContentLine {
    pub(crate) fn new(text: impl Into<String>, style: MessageStyle) -> Self {
        Self {
            rendered: text.into(),
            style,
            override_style: None,
        }
    }
}

pub(crate) fn render_left_border_panel(
    renderer: &mut AnsiRenderer,
    lines: Vec<PanelContentLine>,
) -> Result<()> {
    for line in lines {
        if let Some(override_style) = line.override_style {
            renderer.line_with_override_style(
                line.style,
                override_style,
                line.rendered.as_str(),
            )?;
        } else {
            renderer.line(line.style, line.rendered.as_str())?;
        }
    }

    Ok(())
}

#[cfg(test)]
pub(crate) fn wrap_text(text: &str, width: usize) -> Vec<String> {
    if width == 0 {
        return vec![text.to_string()];
    }

    let mut lines = Vec::new();
    let mut current_line = String::new();

    for word in text.split_whitespace() {
        let word_len = word.chars().count();

        if !current_line.is_empty() {
            let current_visible = current_line
                .chars()
                .filter(|ch| !ch.is_whitespace())
                .count();

            if current_visible + word_len > width {
                lines.push(current_line);
                current_line = String::new();
            }
        }

        if word_len > width {
            if !current_line.is_empty() {
                lines.push(current_line);
                current_line = String::new();
            }

            let mut remaining = word;
            while !remaining.is_empty() {
                let mut byte_len = remaining.len();

                for (chars_taken, (idx, ch)) in remaining.char_indices().enumerate() {
                    if chars_taken == width {
                        byte_len = idx;
                        break;
                    }
                    byte_len = idx + ch.len_utf8();
                }

                let chunk = &remaining[..byte_len];
                lines.push(chunk.to_string());
                remaining = &remaining[byte_len..];
            }
        } else {
            if !current_line.is_empty() {
                current_line.push(' ');
            }
            current_line.push_str(word);
        }
    }

    if !current_line.is_empty() {
        lines.push(current_line);
    }

    if lines.is_empty() {
        lines.push(text.to_string());
    }

    lines
}

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

    #[test]
    fn test_wrap_text() {
        let result = wrap_text("Hello world this is a long text", 10);
        assert_eq!(result, vec!["Hello world", "this is a", "long text"]);

        let result = wrap_text("supercalifragilisticexpialidocious", 10);
        assert_eq!(
            result,
            vec!["supercalif", "ragilistic", "expialidoc", "ious",]
        );

        let result = wrap_text("", 10);
        assert_eq!(result, vec!["".to_string()]);

        let result = wrap_text("Hello", 10);
        assert_eq!(result, vec!["Hello"]);
    }
}