pinix 0.3.0

Progress In Nix - Pacman inspired frontend for Nix
Documentation
use std::rc::Rc;

use console::style;
use indicatif::ProgressBar;

use crate::action::{Action, BuildStepId, ResultFields};
use crate::state::{Handler, HandlerResult, State};
use crate::style::template_style;

#[derive(Default)]
pub struct LogHandler {
    id: BuildStepId,
    logs: Vec<String>,
    logs_window: Option<Rc<LogsWindow>>,
}

impl LogHandler {
    pub fn new(id: BuildStepId) -> Self {
        Self {
            id,
            logs: Vec::new(),
            logs_window: None,
        }
    }

    pub fn with_logs_window(mut self, logs_window: Rc<LogsWindow>) -> Self {
        self.logs_window = Some(logs_window);
        self
    }
}

impl Handler for LogHandler {
    fn on_action(&mut self, state: &mut State, action: &Action) -> anyhow::Result<HandlerResult> {
        match action {
            Action::Result {
                id,
                fields: ResultFields::BuildLogLine(msg),
                ..
            } if *id == self.id => {
                self.logs.push(msg.to_string());

                if let Some(logs_window) = &self.logs_window {
                    logs_window.log(msg.to_string());
                }
            }

            Action::Stop { id } if *id == self.id => {
                let truncated = (state.cmd.args.log_history_len)
                    .map(|max_len| self.logs.len().saturating_sub(max_len))
                    .unwrap_or(0);

                if truncated > 0 {
                    self.logs = self.logs.split_off(truncated);

                    state.println(
                        style(format!(
                            "┆ ⋅⋅⋅ {}",
                            style(format!("(skipped {truncated} lines)")).italic()
                        ))
                        .dim()
                        .to_string(),
                    )?;
                }

                let logs_len = self.logs.len();

                for (i, line) in self.logs.iter().enumerate() {
                    let prefix = if i + 1 == logs_len { '' } else { '' };
                    state.println(style(format!("{prefix} {line}")).dim().to_string())?;
                }

                return Ok(HandlerResult::Close);
            }

            _ => {}
        }

        Ok(HandlerResult::Continue)
    }
}

pub struct LogsWindow {
    log_lines: Vec<ProgressBar>,
}

impl LogsWindow {
    pub fn new(state: &mut State, after: &ProgressBar) -> Self {
        let nb_lines = state.cmd.args.log_window_len;
        let mut log_lines = Vec::new();

        for i in 0..nb_lines {
            let prefix = if i + 1 == nb_lines { "" } else { "" };
            let prev = log_lines.last().unwrap_or(after);

            let next = state.multi_progress.insert_after(
                prev,
                ProgressBar::new_spinner()
                    .with_style(template_style(
                        state.term_size,
                        false,
                        |_| style("{prefix} {wide_msg}").dim(),
                        |_| "",
                    ))
                    .with_prefix(prefix),
            );

            log_lines.push(next);
        }

        Self { log_lines }
    }

    pub fn log(&self, msg: String) {
        for (prev, next) in self.log_lines.iter().zip(&self.log_lines[1..]) {
            if !next.message().is_empty() {
                prev.set_message(next.message());
            }
        }

        if let Some(last_line) = self.log_lines.last() {
            last_line.set_message(msg);
        }
    }

    pub fn resize(&self, _size: u16) {
        for line in &self.log_lines {
            line.tick();
        }
    }
}