tmux-sessionizer 0.6.1

Tmux Sessionizer is a cli tool to fuzzy find all git repositories in a list of specified folders and open them as a new tmux session. Also, for git worktrees, this tool opens all checked out worktrees as new windows.
Documentation
use crossterm::style::Colored;
use ratatui::{
    buffer::Buffer,
    layout::{Direction, Rect},
    style::{Color, Style, Stylize},
    text::{Line, Span, Text},
    widgets::{Block, Borders, Paragraph, Widget, Wrap},
};

pub struct PreviewWidget {
    buffer: String,
    border_color: Color,
    direction: Direction,
}

impl PreviewWidget {
    pub fn new(buffer: String, border_color: Color, direction: Direction) -> Self {
        Self {
            buffer,
            border_color,
            direction,
        }
    }
}

impl Widget for PreviewWidget {
    fn render(self, area: Rect, buf: &mut Buffer)
    where
        Self: Sized,
    {
        let text = str_to_text(&self.buffer, (area.width - 1).into());
        let border_position = if self.direction == Direction::Horizontal {
            Borders::LEFT
        } else {
            Borders::BOTTOM
        };

        Paragraph::new(text)
            .block(
                Block::default()
                    .borders(border_position)
                    .border_style(Style::default().fg(self.border_color)),
            )
            .wrap(Wrap { trim: false })
            .render(area, buf);
    }
}

fn str_to_text(s: &str, max: usize) -> Text<'_> {
    let mut text = Text::default();
    let mut style = Style::default();
    let mut tspan = String::new();
    let mut ansi_state;

    for l in s.lines() {
        let mut line = Line::default();
        ansi_state = false;

        for (i, ch) in l.chars().enumerate() {
            if !ansi_state {
                if ch == '\x1b' && l.chars().nth(i + 1) == Some('[') {
                    if !tspan.is_empty() {
                        let span = Span::styled(tspan.clone(), style);
                        line.spans.push(span);
                    }

                    tspan.clear();
                    ansi_state = true;
                } else {
                    tspan.push(ch);

                    if (line.width() + tspan.chars().count()) == max || i == (l.chars().count() - 1)
                    {
                        let span = Span::styled(tspan.clone(), style);
                        line.spans.push(span);
                        tspan.clear();
                        break;
                    }
                }
            } else {
                match ch {
                    '[' => {}
                    'm' => {
                        style = match tspan.as_str() {
                            "" => style.reset(),
                            "0" => style.reset(),
                            "1" => style.bold(),
                            "3" => style.italic(),
                            "4" => style.underlined(),
                            "5" => style.rapid_blink(),
                            "6" => style.slow_blink(),
                            "7" => style.reversed(),
                            "9" => style.crossed_out(),
                            "22" => style.not_bold(),
                            "23" => style.not_italic(),
                            "24" => style.not_underlined(),
                            "25" => style.not_rapid_blink().not_slow_blink(),
                            "27" => style.not_reversed(),
                            "29" => style.not_crossed_out(),
                            "30" => style.fg(Color::Black),
                            "31" => style.fg(Color::Red),
                            "32" => style.fg(Color::Green),
                            "33" => style.fg(Color::Yellow),
                            "34" => style.fg(Color::Blue),
                            "35" => style.fg(Color::Magenta),
                            "36" => style.fg(Color::Cyan),
                            "37" => style.fg(Color::Gray),
                            "40" => style.bg(Color::Black),
                            "41" => style.bg(Color::Red),
                            "42" => style.bg(Color::Green),
                            "43" => style.bg(Color::Yellow),
                            "44" => style.bg(Color::Blue),
                            "45" => style.bg(Color::Magenta),
                            "46" => style.bg(Color::Cyan),
                            "47" => style.bg(Color::Gray),
                            "90" | "100" => style.fg(Color::DarkGray),
                            "91" | "101" => style.fg(Color::LightRed),
                            "92" | "102" => style.fg(Color::LightGreen),
                            "93" | "103" => style.fg(Color::LightYellow),
                            "94" | "104" => style.fg(Color::LightBlue),
                            "95" | "105" => style.fg(Color::LightMagenta),
                            "96" | "106" => style.fg(Color::LightCyan),
                            "97" | "107" => style.fg(Color::White),
                            code => {
                                if let Some(colored) = Colored::parse_ansi(code) {
                                    match colored {
                                        Colored::ForegroundColor(c) => style.fg(c.into()),
                                        Colored::BackgroundColor(c) => style.bg(c.into()),
                                        Colored::UnderlineColor(c) => {
                                            style.underline_color(c.into())
                                        }
                                    }
                                } else {
                                    style
                                }
                            }
                        };

                        tspan.clear();
                        ansi_state = false;
                    }
                    _ => tspan.push(ch),
                }
            }
        }

        text.lines.push(line);
    }

    text
}