cdx 0.1.24

Library and application for text file manipulation and command line data mining, a little like the gnu textutils
//! Format plain text to a fixed width terminal

use crate::prelude::*;
use crate::*;
use unicode_truncate::UnicodeTruncateStr;
use unicode_width::UnicodeWidthStr;

#[derive(Debug, Default, Copy, Clone)]
/// the size of a box on the screen
pub struct Rect {
    /// width in character
    pub width: usize,
    /// height in lines
    pub height: usize,
}
impl Rect {
    /// new
    #[must_use]
    pub const fn new(width: usize, height: usize) -> Self {
        Self { width, height }
    }
    /// get screen size
    #[must_use]
    pub fn from_screen() -> Self {
        let mut ws = libc::winsize { ws_row: 0, ws_col: 0, ws_xpixel: 0, ws_ypixel: 0 };
        // SAFETY: Yes, it's a call to ioctl.
        if unsafe { libc::ioctl(2, libc::TIOCGWINSZ, &mut ws) } >= 0 {
            Self { width: ws.ws_col as usize, height: (ws.ws_row - 1) as usize }
        } else {
            Self { width: 100, height: 24 }
        }
    }
}

fn dec_max(sizes: &mut [usize]) {
    let mut max_pos = 0;
    let mut max = 0;
    for (i, x) in sizes.iter().enumerate() {
        if *x > max {
            max = *x;
            max_pos = i;
        }
    }
    sizes[max_pos] -= 1;
}

/// show the file on the screen
pub fn basic(file: &str, config: &input_file::Config) -> Result<()> {
    let mut x = Rect::from_screen();
    x.width -= 1;
    show(file, &x, config)
}
/// show the file in a specific rectangle
pub fn show(file: &str, screen: &Rect, config: &input_file::Config) -> Result<()> {
    let mut f = TextFile::new(file, config)?;
    if f.is_empty() {
        return Ok(());
    }
    let mut lines: Vec<Vec<String>> = Vec::new();
    let mut sizes: Vec<usize> = vec![0; f.names().len()];
    if f.has_header() {
        lines.push(f.names().into());
    }
    if !f.is_done() {
        while lines.len() < screen.height {
            lines.push(f.as_strings());
            if f.get_line()? {
                break;
            }
        }
    }
    for x in &lines {
        for (i, c) in x.iter().enumerate() {
            let width = UnicodeWidthStr::width(c.as_str());
            if sizes[i] < width {
                sizes[i] = width;
            }
        }
    }
    let mut total: usize = sizes.iter().sum();
    let target = screen.width - sizes.len();
    while total > target {
        dec_max(&mut sizes);
        total = sizes.iter().sum();
    }
    let mut w = get_writer("-")?;
    let mut do_center = f.has_header();
    for x in &lines {
        let mut need_space = false;
        for (c, y) in x.iter().enumerate() {
            let (nstr, width) = y.unicode_truncate(sizes[c]);
            if need_space {
                w.write_all(b" ")?;
            }
            let num = (sizes[c] - width) / 2;
            if do_center {
                for _ in 0..num {
                    w.write_all(b" ")?;
                }
            }
            w.write_all(nstr.as_bytes())?;
            if do_center {
                let num2 = (sizes[c] - width) - num;
                for _ in 0..num2 {
                    w.write_all(b" ")?;
                }
            } else {
                for _ in width..sizes[c] {
                    w.write_all(b" ")?;
                }
            }
            need_space = true;
        }
        do_center = false;
        w.write_all(b"\n")?;
    }
    Ok(())
}

/// show the file in a specific rectangle
pub fn show2(
    file: &str,
    screen: &Rect,
    w: &mut Vec<String>,
    config: &input_file::Config,
) -> Result<usize> {
    let mut f = TextFile::new(file, config)?;
    if f.is_empty() {
        return Ok(0);
    }
    let mut lines: Vec<Vec<String>> = Vec::new();
    let mut sizes: Vec<usize> = vec![0; f.names().len()];
    if f.has_header() {
        lines.push(f.names().into());
    }
    if !f.is_done() {
        while lines.len() < screen.height {
            lines.push(f.as_strings());
            if f.get_line()? {
                break;
            }
        }
    }
    for x in &lines {
        for (i, c) in x.iter().enumerate() {
            let width = UnicodeWidthStr::width(c.as_str());
            if sizes[i] < width {
                sizes[i] = width;
            }
        }
    }
    let mut total: usize = sizes.iter().sum();
    let target = screen.width - sizes.len();
    while total > target {
        dec_max(&mut sizes);
        total = sizes.iter().sum();
    }

    let mut do_center = f.has_header();
    w.clear();
    for x in &lines {
        let mut s = String::new();
        let mut need_space = false;
        for (c, y) in x.iter().enumerate() {
            let (nstr, width) = y.unicode_truncate(sizes[c]);
            if need_space {
                s.push(' ');
            }
            let num = (sizes[c] - width) / 2;
            if do_center {
                for _ in 0..num {
                    s.push(' ');
                }
            }
            s.push_str(nstr);
            if do_center {
                let num2 = (sizes[c] - width) - num;
                for _ in 0..num2 {
                    s.push(' ');
                }
            } else {
                for _ in width..sizes[c] {
                    s.push(' ');
                }
            }
            need_space = true;
        }
        do_center = false;
        w.push(s);
    }
    Ok(sizes.iter().sum::<usize>() + sizes.len())
}