use std::{
borrow::{Borrow, Cow},
cmp, fmt,
};
use crate::{
ansi::{ANSIBuf, ANSIFmt},
colors::Colors,
config::{spanned::SpannedConfig, Formatting},
config::{AlignmentHorizontal, AlignmentVertical, Indent, Offset, Position, Sides},
dimension::Dimension,
grid::writable::Typewriter,
records::{ExactRecords, PeekableRecords, Records},
util::string::get_line_width,
};
#[derive(Debug, Clone)]
pub struct WritableGrid<R, G, D, C> {
records: R,
config: G,
dimension: D,
colors: C,
}
impl<R, G, D, C> WritableGrid<R, G, D, C> {
pub fn new(records: R, config: G, dimension: D, colors: C) -> Self {
WritableGrid {
records,
config,
dimension,
colors,
}
}
}
impl<R, G, D, C> WritableGrid<R, G, D, C> {
pub fn build<F>(&self, mut f: F) -> fmt::Result
where
R: Records + PeekableRecords + ExactRecords,
D: Dimension,
C: Colors,
G: Borrow<SpannedConfig>,
F: Typewriter,
{
if self.records.count_columns() == 0 || self.records.hint_count_rows() == Some(0) {
return Ok(());
}
let ctx = PrintCtx {
cfg: self.config.borrow(),
colors: &self.colors,
dims: &self.dimension,
records: &self.records,
};
print_grid(&mut f, ctx)
}
}
#[derive(Debug, Copy, Clone)]
struct PrintCtx<'a, R, D, C> {
records: &'a R,
cfg: &'a SpannedConfig,
dims: &'a D,
colors: &'a C,
}
fn print_grid<F, R, D, C>(f: &mut F, ctx: PrintCtx<'_, R, D, C>) -> fmt::Result
where
F: Typewriter,
R: Records + PeekableRecords + ExactRecords,
D: Dimension,
C: Colors,
{
let has_spans = ctx.cfg.has_column_spans() || ctx.cfg.has_row_spans();
if has_spans {
return grid_spanned::build_grid(f, ctx);
}
let is_basic = !ctx.cfg.has_border_colors()
&& !ctx.cfg.has_padding_color()
&& !ctx.cfg.has_justification()
&& !ctx.cfg.has_offset_chars()
&& !has_margin(ctx.cfg)
&& ctx.colors.is_empty();
if is_basic {
grid_basic::build_grid(f, ctx)
} else {
grid_not_spanned::build_grid(f, ctx)
}
}
fn has_margin(cfg: &SpannedConfig) -> bool {
let margin = cfg.get_margin();
margin.left.size > 0 || margin.right.size > 0 || margin.top.size > 0 || margin.bottom.size > 0
}
mod grid_basic {
use super::*;
struct TextCfg {
alignment: AlignmentHorizontal,
formatting: Formatting,
justification: char,
}
#[derive(Debug, Clone, Copy)]
struct Shape {
count_rows: usize,
count_columns: usize,
}
struct HIndent {
left: usize,
right: usize,
}
pub(super) fn build_grid<F, R, D, C>(f: &mut F, ctx: PrintCtx<'_, R, D, C>) -> fmt::Result
where
F: Typewriter,
R: Records + PeekableRecords + ExactRecords,
D: Dimension,
{
let shape = Shape {
count_rows: ctx.records.count_rows(),
count_columns: ctx.records.count_columns(),
};
let mut new_line = false;
for row in 0..shape.count_rows {
let height = ctx.dims.get_height(row);
let has_horizontal = ctx.cfg.has_horizontal(row, shape.count_rows);
if new_line && (has_horizontal || height > 0) {
f.reset()?;
new_line = false;
}
if has_horizontal {
print_split_line(f, ctx.cfg, ctx.dims, row, shape)?;
if height > 0 {
f.reset()?;
} else {
new_line = true;
}
}
if height > 0 {
print_grid_line(f, &ctx, shape, height, row, 0)?;
for i in 1..height {
f.reset()?;
print_grid_line(f, &ctx, shape, height, row, i)?;
}
new_line = true;
}
}
if ctx.cfg.has_horizontal(shape.count_rows, shape.count_rows) {
f.reset()?;
print_split_line(f, ctx.cfg, ctx.dims, shape.count_rows, shape)?;
}
Ok(())
}
fn print_grid_line<F, R, D, C>(
f: &mut F,
ctx: &PrintCtx<'_, R, D, C>,
shape: Shape,
height: usize,
row: usize,
line: usize,
) -> fmt::Result
where
F: Typewriter,
R: Records + PeekableRecords + ExactRecords,
D: Dimension,
{
for col in 0..shape.count_columns {
let pos = (row, col).into();
print_vertical_char(f, ctx.cfg, pos, shape.count_columns)?;
print_cell_line(f, ctx, height, pos, line)?;
}
let pos = (row, shape.count_columns).into();
print_vertical_char(f, ctx.cfg, pos, shape.count_columns)?;
Ok(())
}
fn print_split_line<F, D>(
f: &mut F,
cfg: &SpannedConfig,
dimension: &D,
row: usize,
shape: Shape,
) -> fmt::Result
where
F: Typewriter,
D: Dimension,
{
print_vertical_intersection(f, cfg, (row, 0).into(), shape)?;
for col in 0..shape.count_columns {
let width = dimension.get_width(col);
if width > 0 {
let pos = (row, col).into();
let main = cfg.get_horizontal(pos, shape.count_rows);
match main {
Some(c) => repeat_char(f, c, width)?,
None => repeat_char(f, ' ', width)?,
}
}
let pos = (row, col + 1).into();
print_vertical_intersection(f, cfg, pos, shape)?;
}
Ok(())
}
fn print_vertical_intersection<F>(
f: &mut F,
cfg: &SpannedConfig,
pos: Position,
shape: Shape,
) -> fmt::Result
where
F: Typewriter,
{
let intersection = cfg.get_intersection(pos, (shape.count_rows, shape.count_columns));
let intersection = match intersection {
Some(c) => c,
None => return Ok(()),
};
if !cfg.has_vertical(pos.col, shape.count_columns) {
return Ok(());
}
f.write_char(intersection)?;
Ok(())
}
fn print_vertical_char<F>(
f: &mut F,
cfg: &SpannedConfig,
pos: Position,
count_columns: usize,
) -> fmt::Result
where
F: Typewriter,
{
let symbol = match cfg.get_vertical(pos, count_columns) {
Some(c) => c,
None => return Ok(()),
};
f.write_char(symbol)?;
Ok(())
}
fn print_cell_line<F, R, D, C>(
f: &mut F,
ctx: &PrintCtx<'_, R, D, C>,
height: usize,
pos: Position,
line: usize,
) -> fmt::Result
where
F: Typewriter,
R: Records + PeekableRecords + ExactRecords,
D: Dimension,
{
let width = ctx.dims.get_width(pos.col);
let pad = ctx.cfg.get_padding(pos);
let valignment = *ctx.cfg.get_alignment_vertical(pos);
let text_cfg = TextCfg {
alignment: *ctx.cfg.get_alignment_horizontal(pos),
formatting: ctx.cfg.get_formatting(pos),
justification: ctx.cfg.get_justification(pos),
};
let mut cell_height = ctx.records.count_lines(pos);
if text_cfg.formatting.vertical_trim {
cell_height -= count_empty_lines_at_start(ctx.records, pos)
+ count_empty_lines_at_end(ctx.records, pos);
}
if cell_height > height {
cell_height = height;
}
let indent = top_indent(pad, valignment, cell_height, height);
if indent > line {
return repeat_char(f, pad.top.fill, width);
}
let mut index = line - indent;
let cell_has_this_line = cell_height > index;
if !cell_has_this_line {
return repeat_char(f, pad.bottom.fill, width);
}
if text_cfg.formatting.vertical_trim {
let empty_lines = count_empty_lines_at_start(ctx.records, pos);
index += empty_lines;
if index > ctx.records.count_lines(pos) {
return repeat_char(f, pad.top.fill, width);
}
}
let width = width - pad.left.size - pad.right.size;
repeat_char(f, pad.left.fill, pad.left.size)?;
print_line(f, ctx.records, pos, index, width, text_cfg)?;
repeat_char(f, pad.right.fill, pad.right.size)?;
Ok(())
}
fn print_line<F, R>(
f: &mut F,
records: &R,
pos: Position,
index: usize,
available: usize,
cfg: TextCfg,
) -> fmt::Result
where
F: Typewriter,
R: Records + PeekableRecords,
{
let line = records.get_line(pos, index);
let (line, line_width) = if cfg.formatting.horizontal_trim {
let line = string_trim(line);
let width = get_line_width(&line);
(line, width)
} else {
let width = records.get_line_width(pos, index);
(Cow::Borrowed(line), width)
};
if cfg.formatting.allow_lines_alignment {
let indent = calculate_indent(cfg.alignment, line_width, available);
return print_text_padded(f, &line, cfg.justification, indent, line_width);
}
let cell_width = if cfg.formatting.horizontal_trim {
(0..records.count_lines(pos))
.map(|i| records.get_line(pos, i))
.map(|line| get_line_width(line.trim()))
.max()
.unwrap_or_default()
} else {
records.get_width(pos)
};
let indent = calculate_indent(cfg.alignment, cell_width, available);
print_text_padded(f, &line, cfg.justification, indent, line_width)?;
let rest_width = cell_width - line_width;
repeat_char(f, cfg.justification, rest_width)?;
Ok(())
}
fn print_text_padded<F>(
f: &mut F,
text: &str,
space: char,
indent: HIndent,
width: usize,
) -> fmt::Result
where
F: Typewriter,
{
repeat_char(f, space, indent.left)?;
f.write_str(text, width)?;
repeat_char(f, space, indent.right)?;
Ok(())
}
fn top_indent(
pad: &Sides<Indent>,
alignment: AlignmentVertical,
height: usize,
available: usize,
) -> usize {
let available = available - pad.top.size;
let indent = indent_from_top(alignment, available, height);
indent + pad.top.size
}
fn indent_from_top(alignment: AlignmentVertical, available: usize, real: usize) -> usize {
match alignment {
AlignmentVertical::Top => 0,
AlignmentVertical::Bottom => available - real,
AlignmentVertical::Center => (available - real) / 2,
}
}
fn calculate_indent(alignment: AlignmentHorizontal, width: usize, available: usize) -> HIndent {
let diff = available - width;
let (left, right) = match alignment {
AlignmentHorizontal::Left => (0, diff),
AlignmentHorizontal::Right => (diff, 0),
AlignmentHorizontal::Center => {
let left = diff / 2;
let rest = diff - left;
(left, rest)
}
};
HIndent { left, right }
}
fn repeat_char<F>(f: &mut F, c: char, n: usize) -> fmt::Result
where
F: Typewriter,
{
for _ in 0..n {
f.write_char(c)?;
}
Ok(())
}
fn count_empty_lines_at_end<R>(records: &R, pos: Position) -> usize
where
R: Records + PeekableRecords,
{
(0..records.count_lines(pos))
.map(|i| records.get_line(pos, i))
.rev()
.take_while(|l| l.trim().is_empty())
.count()
}
fn count_empty_lines_at_start<R>(records: &R, pos: Position) -> usize
where
R: Records + PeekableRecords,
{
(0..records.count_lines(pos))
.map(|i| records.get_line(pos, i))
.take_while(|s| s.trim().is_empty())
.count()
}
fn string_trim(text: &str) -> Cow<'_, str> {
#[cfg(feature = "ansi")]
{
ansi_str::AnsiStr::ansi_trim(text)
}
#[cfg(not(feature = "ansi"))]
{
text.trim().into()
}
}
}
mod grid_not_spanned {
use super::*;
struct TextCfg<C, C1> {
alignment: AlignmentHorizontal,
formatting: Formatting,
color: Option<C>,
justification: Colored<char, C1>,
}
struct Colored<T, C> {
data: T,
color: Option<C>,
}
impl<T, C> Colored<T, C> {
fn new(data: T, color: Option<C>) -> Self {
Self { data, color }
}
}
#[derive(Debug, Clone, Copy)]
struct Shape {
count_rows: usize,
count_columns: usize,
}
struct HIndent {
left: usize,
right: usize,
}
pub(super) fn build_grid<F, R, D, C>(f: &mut F, ctx: PrintCtx<'_, R, D, C>) -> fmt::Result
where
F: Typewriter,
R: Records + PeekableRecords + ExactRecords,
D: Dimension,
C: Colors,
{
let shape = Shape {
count_rows: ctx.records.count_rows(),
count_columns: ctx.records.count_columns(),
};
let total_width = total_width(ctx.cfg, ctx.dims, shape.count_columns);
let margin = ctx.cfg.get_margin();
let total_width_with_margin = total_width + margin.left.size + margin.right.size;
let total_height = total_height(ctx.cfg, ctx.dims, shape.count_rows);
if margin.top.size > 0 {
print_margin_top(f, ctx.cfg, total_width_with_margin)?;
f.reset()?;
}
let mut table_line = 0;
let mut prev_empty_horizontal = false;
for row in 0..shape.count_rows {
let height = ctx.dims.get_height(row);
if ctx.cfg.has_horizontal(row, shape.count_rows) {
if prev_empty_horizontal {
f.reset()?;
}
print_margin_left(f, ctx.cfg, table_line, total_height)?;
print_split_line(f, ctx.cfg, ctx.dims, row, shape)?;
print_margin_right(f, ctx.cfg, table_line, total_height)?;
if height > 0 {
f.reset()?;
prev_empty_horizontal = false;
} else {
prev_empty_horizontal = true;
}
table_line += 1;
} else if height > 0 && prev_empty_horizontal {
f.reset()?;
prev_empty_horizontal = false;
}
for i in 0..height {
print_margin_left(f, ctx.cfg, table_line, total_height)?;
for col in 0..shape.count_columns {
let pos = (row, col).into();
print_vertical_char(f, ctx.cfg, pos, i, height, shape.count_columns)?;
print_cell_line(f, &ctx, height, pos, i)?;
let is_last_column = col + 1 == shape.count_columns;
if is_last_column {
let pos = (row, col + 1).into();
print_vertical_char(f, ctx.cfg, pos, i, height, shape.count_columns)?;
}
}
print_margin_right(f, ctx.cfg, table_line, total_height)?;
let is_last_line = i + 1 == height;
let is_last_row = row + 1 == shape.count_rows;
if !(is_last_line && is_last_row) {
f.reset()?;
}
table_line += 1;
}
}
if ctx.cfg.has_horizontal(shape.count_rows, shape.count_rows) {
f.reset()?;
print_margin_left(f, ctx.cfg, table_line, total_height)?;
print_split_line(f, ctx.cfg, ctx.dims, shape.count_rows, shape)?;
print_margin_right(f, ctx.cfg, table_line, total_height)?;
}
if margin.bottom.size > 0 {
f.reset()?;
print_margin_bottom(f, ctx.cfg, total_width_with_margin)?;
}
Ok(())
}
fn print_split_line<F, D>(
f: &mut F,
cfg: &SpannedConfig,
dimension: &D,
row: usize,
shape: Shape,
) -> fmt::Result
where
F: Typewriter,
D: Dimension,
{
let mut used_color = None;
print_vertical_intersection(f, cfg, (row, 0).into(), shape, &mut used_color)?;
for col in 0..shape.count_columns {
let width = dimension.get_width(col);
if width > 0 {
let pos = (row, col).into();
let main = cfg.get_horizontal(pos, shape.count_rows);
match main {
Some(c) => {
let color = cfg.get_horizontal_color(pos, shape.count_rows);
prepare_coloring(f, color, &mut used_color)?;
print_horizontal_border(f, cfg, pos, width, c, &used_color)?;
}
None => repeat_char(f, ' ', width)?,
}
}
let pos = (row, col + 1).into();
print_vertical_intersection(f, cfg, pos, shape, &mut used_color)?;
}
if let Some(color) = used_color.take() {
f.colorize_stop(color)?;
}
Ok(())
}
fn print_vertical_intersection<'a, F>(
f: &mut F,
cfg: &'a SpannedConfig,
pos: Position,
shape: Shape,
used_color: &mut Option<&'a ANSIBuf>,
) -> fmt::Result
where
F: Typewriter,
{
let intersection = match cfg.get_intersection(pos, (shape.count_rows, shape.count_columns))
{
Some(c) => c,
None => return Ok(()),
};
if !cfg.has_vertical(pos.col, shape.count_columns) {
return Ok(());
}
let color = cfg.get_intersection_color(pos, (shape.count_rows, shape.count_columns));
prepare_coloring(f, color, used_color)?;
f.write_char(intersection)?;
Ok(())
}
fn prepare_coloring<'a, F>(
f: &mut F,
new_color: Option<&'a ANSIBuf>,
old_color: &mut Option<&'a ANSIBuf>,
) -> fmt::Result
where
F: Typewriter,
{
match new_color {
Some(new_color) => match old_color.as_mut() {
Some(old_color) => {
if **old_color != *new_color {
f.colorize_stop(*old_color)?;
f.colorize_start(new_color)?;
*old_color = new_color;
}
}
None => {
f.colorize_start(new_color)?;
*old_color = Some(new_color);
}
},
None => {
if let Some(old_color) = old_color.take() {
f.colorize_stop(old_color)?;
}
}
}
Ok(())
}
fn print_vertical_char<F>(
f: &mut F,
cfg: &SpannedConfig,
pos: Position,
line: usize,
count_lines: usize,
count_columns: usize,
) -> fmt::Result
where
F: Typewriter,
{
let symbol = match cfg.get_vertical(pos, count_columns) {
Some(c) => c,
None => return Ok(()),
};
let symbol = cfg
.lookup_vertical_char(pos, line, count_lines)
.unwrap_or(symbol);
let color = cfg
.get_vertical_color(pos, count_columns)
.or_else(|| cfg.lookup_vertical_color(pos, line, count_lines));
match color {
Some(color) => {
f.colorize_start(color)?;
f.write_char(symbol)?;
f.colorize_stop(color)?;
}
None => f.write_char(symbol)?,
}
Ok(())
}
fn print_horizontal_border<F>(
f: &mut F,
cfg: &SpannedConfig,
pos: Position,
width: usize,
c: char,
used_color: &Option<&ANSIBuf>,
) -> fmt::Result
where
F: Typewriter,
{
if !cfg.is_overridden_horizontal(pos) {
return repeat_char(f, c, width);
}
for i in 0..width {
let c = cfg.lookup_horizontal_char(pos, i, width).unwrap_or(c);
let color = match cfg.lookup_horizontal_color(pos, i, width) {
Some(color) => color,
None => {
f.write_char(c)?;
continue;
}
};
match used_color {
Some(old_color) => {
f.colorize_stop(old_color)?;
f.colorize_start(color)?;
f.write_char(c)?;
f.colorize_stop(color)?;
f.colorize_start(old_color)?;
}
None => {
f.colorize_start(color)?;
f.write_char(c)?;
f.colorize_stop(color)?;
}
}
}
Ok(())
}
fn print_cell_line<F, R, D, C>(
f: &mut F,
ctx: &PrintCtx<'_, R, D, C>,
height: usize,
pos: Position,
line: usize,
) -> fmt::Result
where
F: Typewriter,
R: Records + PeekableRecords + ExactRecords,
C: Colors,
D: Dimension,
{
let width = ctx.dims.get_width(pos.col);
let formatting = ctx.cfg.get_formatting(pos);
let text_cfg = TextCfg {
alignment: *ctx.cfg.get_alignment_horizontal(pos),
color: ctx.colors.get_color(pos),
justification: Colored::new(
ctx.cfg.get_justification(pos),
ctx.cfg.get_justification_color(pos),
),
formatting,
};
let pad = ctx.cfg.get_padding(pos);
let pad_color = ctx.cfg.get_padding_color(pos);
let valignment = *ctx.cfg.get_alignment_vertical(pos);
let mut cell_height = ctx.records.count_lines(pos);
if formatting.vertical_trim {
cell_height -= count_empty_lines_at_start(ctx.records, pos)
+ count_empty_lines_at_end(ctx.records, pos);
}
if cell_height > height {
cell_height = height;
}
let indent = top_indent(pad, valignment, cell_height, height);
if indent > line {
return print_indent(f, pad.top.fill, width, pad_color.top.as_ref());
}
let mut index = line - indent;
let cell_has_this_line = cell_height > index;
if !cell_has_this_line {
return print_indent(f, pad.bottom.fill, width, pad_color.bottom.as_ref());
}
if formatting.vertical_trim {
let empty_lines = count_empty_lines_at_start(ctx.records, pos);
index += empty_lines;
if index > ctx.records.count_lines(pos) {
return print_indent(f, pad.top.fill, width, pad_color.top.as_ref());
}
}
let width = width - pad.left.size - pad.right.size;
print_indent(f, pad.left.fill, pad.left.size, pad_color.left.as_ref())?;
print_line(f, ctx.records, pos, index, width, text_cfg)?;
print_indent(f, pad.right.fill, pad.right.size, pad_color.right.as_ref())?;
Ok(())
}
fn print_line<F, R, C>(
f: &mut F,
records: &R,
pos: Position,
index: usize,
available: usize,
cfg: TextCfg<C, &'_ ANSIBuf>,
) -> fmt::Result
where
F: Typewriter,
R: Records + PeekableRecords,
C: ANSIFmt,
{
let line = records.get_line(pos, index);
let (line, line_width) = if cfg.formatting.horizontal_trim {
let line = string_trim(line);
let width = get_line_width(&line);
(line, width)
} else {
let width = records.get_line_width(pos, index);
(Cow::Borrowed(line), width)
};
if cfg.formatting.allow_lines_alignment {
let indent = calculate_indent(cfg.alignment, line_width, available);
let text = Colored::new(line.as_ref(), cfg.color);
return print_text_padded(f, text, &cfg.justification, indent, line_width);
}
let cell_width = if cfg.formatting.horizontal_trim {
(0..records.count_lines(pos))
.map(|i| records.get_line(pos, i))
.map(|line| get_line_width(line.trim()))
.max()
.unwrap_or_default()
} else {
records.get_width(pos)
};
let indent = calculate_indent(cfg.alignment, cell_width, available);
let text = Colored::new(line.as_ref(), cfg.color);
print_text_padded(f, text, &cfg.justification, indent, line_width)?;
let rest_width = cell_width - line_width;
print_indent2(f, &cfg.justification, rest_width)?;
Ok(())
}
fn print_text_padded<F, C, C1>(
f: &mut F,
text: Colored<&str, C>,
justification: &Colored<char, C1>,
indent: HIndent,
width: usize,
) -> fmt::Result
where
F: Typewriter,
C: ANSIFmt,
C1: ANSIFmt,
{
print_indent2(f, justification, indent.left)?;
print_text(f, text, width)?;
print_indent2(f, justification, indent.right)?;
Ok(())
}
fn print_text<F, C>(f: &mut F, text: Colored<&str, C>, width: usize) -> fmt::Result
where
F: Typewriter,
C: ANSIFmt,
{
match text.color {
Some(color) => {
f.colorize_start(&color)?;
f.write_str(text.data, width)?;
f.colorize_stop(&color)?;
}
None => {
f.write_str(text.data, width)?;
}
}
Ok(())
}
fn top_indent(
pad: &Sides<Indent>,
alignment: AlignmentVertical,
height: usize,
available: usize,
) -> usize {
let available = available - pad.top.size;
let indent = indent_from_top(alignment, available, height);
indent + pad.top.size
}
fn indent_from_top(alignment: AlignmentVertical, available: usize, real: usize) -> usize {
match alignment {
AlignmentVertical::Top => 0,
AlignmentVertical::Bottom => available - real,
AlignmentVertical::Center => (available - real) / 2,
}
}
fn calculate_indent(alignment: AlignmentHorizontal, width: usize, available: usize) -> HIndent {
let diff = available - width;
let (left, right) = match alignment {
AlignmentHorizontal::Left => (0, diff),
AlignmentHorizontal::Right => (diff, 0),
AlignmentHorizontal::Center => {
let left = diff / 2;
let rest = diff - left;
(left, rest)
}
};
HIndent { left, right }
}
fn repeat_char<F>(f: &mut F, c: char, n: usize) -> fmt::Result
where
F: Typewriter,
{
for _ in 0..n {
f.write_char(c)?;
}
Ok(())
}
fn count_empty_lines_at_end<R>(records: &R, pos: Position) -> usize
where
R: Records + PeekableRecords,
{
(0..records.count_lines(pos))
.map(|i| records.get_line(pos, i))
.rev()
.take_while(|l| l.trim().is_empty())
.count()
}
fn count_empty_lines_at_start<R>(records: &R, pos: Position) -> usize
where
R: Records + PeekableRecords,
{
(0..records.count_lines(pos))
.map(|i| records.get_line(pos, i))
.take_while(|s| s.trim().is_empty())
.count()
}
fn total_width<D>(cfg: &SpannedConfig, dimension: &D, count_columns: usize) -> usize
where
D: Dimension,
{
(0..count_columns)
.map(|i| dimension.get_width(i))
.sum::<usize>()
+ cfg.count_vertical(count_columns)
}
fn total_height<D>(cfg: &SpannedConfig, dimension: &D, count_rows: usize) -> usize
where
D: Dimension,
{
(0..count_rows)
.map(|i| dimension.get_height(i))
.sum::<usize>()
+ cfg.count_horizontal(count_rows)
}
fn print_margin_top<F>(f: &mut F, cfg: &SpannedConfig, width: usize) -> fmt::Result
where
F: Typewriter,
{
let indent = cfg.get_margin().top;
let offset = cfg.get_margin_offset().top;
let color = cfg.get_margin_color();
let color = color.top;
print_indent_lines(f, indent, offset, color, width)
}
fn print_margin_bottom<F>(f: &mut F, cfg: &SpannedConfig, width: usize) -> fmt::Result
where
F: Typewriter,
{
let indent = cfg.get_margin().bottom;
let offset = cfg.get_margin_offset().bottom;
let color = cfg.get_margin_color();
let color = color.bottom;
print_indent_lines(f, indent, offset, color, width)
}
fn print_margin_left<F>(
f: &mut F,
cfg: &SpannedConfig,
line: usize,
height: usize,
) -> fmt::Result
where
F: Typewriter,
{
let indent = cfg.get_margin().left;
let offset = cfg.get_margin_offset().left;
let color = cfg.get_margin_color();
let color = color.left;
print_margin_vertical(f, indent, offset, color, line, height)
}
fn print_margin_right<F>(
f: &mut F,
cfg: &SpannedConfig,
line: usize,
height: usize,
) -> fmt::Result
where
F: Typewriter,
{
let indent = cfg.get_margin().right;
let offset = cfg.get_margin_offset().right;
let color = cfg.get_margin_color();
let color = color.right;
print_margin_vertical(f, indent, offset, color, line, height)
}
fn print_margin_vertical<F>(
f: &mut F,
indent: Indent,
offset: Offset,
color: Option<&ANSIBuf>,
line: usize,
height: usize,
) -> fmt::Result
where
F: Typewriter,
{
if indent.size == 0 {
return Ok(());
}
match offset {
Offset::Start(offset) => {
let offset = cmp::min(offset, height);
if line >= offset {
print_indent(f, indent.fill, indent.size, color)?;
} else {
repeat_char(f, ' ', indent.size)?;
}
}
Offset::End(offset) => {
let offset = cmp::min(offset, height);
let pos = height - offset;
if line >= pos {
repeat_char(f, ' ', indent.size)?;
} else {
print_indent(f, indent.fill, indent.size, color)?;
}
}
}
Ok(())
}
fn print_indent_lines<F>(
f: &mut F,
indent: Indent,
offset: Offset,
color: Option<&ANSIBuf>,
width: usize,
) -> fmt::Result
where
F: Typewriter,
{
if indent.size == 0 {
return Ok(());
}
let (start_offset, end_offset) = match offset {
Offset::Start(start) => (start, 0),
Offset::End(end) => (0, end),
};
let start_offset = std::cmp::min(start_offset, width);
let end_offset = std::cmp::min(end_offset, width);
let indent_size = width - start_offset - end_offset;
for i in 0..indent.size {
if start_offset > 0 {
repeat_char(f, ' ', start_offset)?;
}
if indent_size > 0 {
print_indent(f, indent.fill, indent_size, color)?;
}
if end_offset > 0 {
repeat_char(f, ' ', end_offset)?;
}
if i + 1 != indent.size {
f.write_char('\n')?;
}
}
Ok(())
}
fn print_indent<F, C>(f: &mut F, c: char, n: usize, color: Option<C>) -> fmt::Result
where
F: Typewriter,
C: ANSIFmt,
{
if n == 0 {
return Ok(());
}
match color {
Some(color) => {
f.colorize_start(&color)?;
repeat_char(f, c, n)?;
f.colorize_stop(&color)?;
Ok(())
}
None => repeat_char(f, c, n),
}
}
fn print_indent2<F, C>(f: &mut F, c: &Colored<char, C>, n: usize) -> fmt::Result
where
F: Typewriter,
C: ANSIFmt,
{
print_indent(f, c.data, n, c.color.as_ref())
}
fn string_trim(text: &str) -> Cow<'_, str> {
#[cfg(feature = "ansi")]
{
ansi_str::AnsiStr::ansi_trim(text)
}
#[cfg(not(feature = "ansi"))]
{
text.trim().into()
}
}
}
mod grid_spanned {
use super::*;
struct TextCfg<C, C1> {
alignment: AlignmentHorizontal,
formatting: Formatting,
color: Option<C>,
justification: Colored<char, C1>,
}
struct Colored<T, C> {
data: T,
color: Option<C>,
}
impl<T, C> Colored<T, C> {
fn new(data: T, color: Option<C>) -> Self {
Self { data, color }
}
}
#[derive(Debug, Copy, Clone)]
struct Shape {
count_rows: usize,
count_columns: usize,
}
struct HIndent {
left: usize,
right: usize,
}
pub(super) fn build_grid<F, R, D, C>(f: &mut F, ctx: PrintCtx<'_, R, D, C>) -> fmt::Result
where
F: Typewriter,
R: Records + PeekableRecords + ExactRecords,
D: Dimension,
C: Colors,
{
let shape = Shape {
count_rows: ctx.records.count_rows(),
count_columns: ctx.records.count_columns(),
};
let total_width = total_width(ctx.cfg, ctx.dims, shape.count_columns);
let margin = ctx.cfg.get_margin();
let total_width_with_margin = total_width + margin.left.size + margin.right.size;
let total_height = total_height(ctx.cfg, ctx.dims, shape.count_rows);
if margin.top.size > 0 {
print_margin_top(f, ctx.cfg, total_width_with_margin)?;
f.write_char('\n')?;
}
let mut table_line = 0;
let mut prev_empty_horizontal = false;
for row in 0..shape.count_rows {
let count_lines = ctx.dims.get_height(row);
if ctx.cfg.has_horizontal(row, shape.count_rows) {
if prev_empty_horizontal {
f.write_char('\n')?;
}
print_margin_left(f, ctx.cfg, table_line, total_height)?;
print_split_line_spanned(f, &ctx, row, shape)?;
print_margin_right(f, ctx.cfg, table_line, total_height)?;
if count_lines > 0 {
f.write_char('\n')?;
prev_empty_horizontal = false;
} else {
prev_empty_horizontal = true;
}
table_line += 1;
} else if count_lines > 0 && prev_empty_horizontal {
f.write_char('\n')?;
prev_empty_horizontal = false;
}
for i in 0..count_lines {
print_margin_left(f, ctx.cfg, table_line, total_height)?;
for col in 0..shape.count_columns {
let pos = (row, col).into();
if ctx.cfg.is_cell_covered_by_both_spans(pos) {
continue;
}
if ctx.cfg.is_cell_covered_by_column_span(pos) {
let is_last_column = col + 1 == shape.count_columns;
if is_last_column {
let pos = (row, col + 1).into();
let count_columns = shape.count_columns;
print_vertical_char(f, ctx.cfg, pos, i, count_lines, count_columns)?;
}
continue;
}
print_vertical_char(f, ctx.cfg, pos, i, count_lines, shape.count_columns)?;
if ctx.cfg.is_cell_covered_by_row_span(pos) {
let original_row = closest_visible_row(ctx.cfg, pos).unwrap();
let mut skip_lines = (original_row..row)
.map(|i| ctx.dims.get_height(i))
.sum::<usize>();
skip_lines += (original_row + 1..=row)
.map(|row| ctx.cfg.has_horizontal(row, shape.count_rows) as usize)
.sum::<usize>();
let line = i + skip_lines;
let pos = (original_row, col).into();
let width = get_cell_width(ctx.cfg, ctx.dims, pos, shape.count_columns);
let height = get_cell_height(ctx.cfg, ctx.dims, pos, shape.count_rows);
print_cell_line(f, &ctx, width, height, pos, line)?;
} else {
let width = get_cell_width(ctx.cfg, ctx.dims, pos, shape.count_columns);
let height = get_cell_height(ctx.cfg, ctx.dims, pos, shape.count_rows);
print_cell_line(f, &ctx, width, height, pos, i)?;
}
let is_last_column = col + 1 == shape.count_columns;
if is_last_column {
let pos = (row, col + 1).into();
print_vertical_char(f, ctx.cfg, pos, i, count_lines, shape.count_columns)?;
}
}
print_margin_right(f, ctx.cfg, table_line, total_height)?;
let is_last_line = i + 1 == count_lines;
let is_last_row = row + 1 == shape.count_rows;
if !(is_last_line && is_last_row) {
f.write_char('\n')?;
}
table_line += 1;
}
}
if ctx.cfg.has_horizontal(shape.count_rows, shape.count_rows) {
f.write_char('\n')?;
print_margin_left(f, ctx.cfg, table_line, total_height)?;
print_split_line(f, ctx.cfg, ctx.dims, shape.count_rows, shape)?;
print_margin_right(f, ctx.cfg, table_line, total_height)?;
}
if margin.bottom.size > 0 {
f.write_char('\n')?;
print_margin_bottom(f, ctx.cfg, total_width_with_margin)?;
}
Ok(())
}
fn print_split_line_spanned<F, R, D, C>(
f: &mut F,
ctx: &PrintCtx<'_, R, D, C>,
row: usize,
shape: Shape,
) -> fmt::Result
where
F: Typewriter,
R: Records + ExactRecords + PeekableRecords,
D: Dimension,
C: Colors,
{
let mut used_color = None;
let pos = (row, 0).into();
print_vertical_intersection(f, ctx.cfg, pos, shape, &mut used_color)?;
for col in 0..shape.count_columns {
let pos = (row, col).into();
if ctx.cfg.is_cell_covered_by_both_spans(pos) {
continue;
}
if ctx.cfg.is_cell_covered_by_row_span(pos) {
prepare_coloring(f, None, &mut used_color)?;
let original_row = closest_visible_row(ctx.cfg, pos).unwrap();
let mut skip_lines = (original_row..row)
.map(|i| ctx.dims.get_height(i))
.sum::<usize>();
if row > 0 {
skip_lines += (original_row..row - 1)
.map(|row| ctx.cfg.has_horizontal(row + 1, shape.count_rows) as usize)
.sum::<usize>();
}
let pos = (original_row, col).into();
let height = get_cell_height(ctx.cfg, ctx.dims, pos, shape.count_rows);
let width = get_cell_width(ctx.cfg, ctx.dims, pos, shape.count_columns);
let line = skip_lines;
print_cell_line(f, ctx, width, height, pos, line)?;
let mut col = col;
if let Some(span) = ctx.cfg.get_column_span(pos) {
col += span - 1;
}
let pos = (row, col + 1).into();
print_vertical_intersection(f, ctx.cfg, pos, shape, &mut used_color)?;
continue;
}
let width = ctx.dims.get_width(col);
if width > 0 {
let main = ctx.cfg.get_horizontal(pos, shape.count_rows);
match main {
Some(c) => {
let color = ctx.cfg.get_horizontal_color(pos, shape.count_rows);
prepare_coloring(f, color, &mut used_color)?;
print_horizontal_border(f, ctx.cfg, pos, width, c, &used_color)?;
}
None => repeat_char(f, ' ', width)?,
}
}
let pos = (row, col + 1).into();
print_vertical_intersection(f, ctx.cfg, pos, shape, &mut used_color)?;
}
if let Some(color) = used_color {
f.colorize_stop(color)?;
}
Ok(())
}
fn print_vertical_char<F>(
f: &mut F,
cfg: &SpannedConfig,
pos: Position,
line: usize,
count_lines: usize,
count_columns: usize,
) -> fmt::Result
where
F: Typewriter,
{
let symbol = match cfg.get_vertical(pos, count_columns) {
Some(c) => c,
None => return Ok(()),
};
let symbol = cfg
.lookup_vertical_char(pos, line, count_lines)
.unwrap_or(symbol);
let color = cfg
.get_vertical_color(pos, count_columns)
.or_else(|| cfg.lookup_vertical_color(pos, line, count_lines));
match color {
Some(color) => {
f.colorize_start(color)?;
f.write_char(symbol)?;
f.colorize_stop(color)?;
}
None => f.write_char(symbol)?,
}
Ok(())
}
fn print_vertical_intersection<'a, F>(
f: &mut F,
cfg: &'a SpannedConfig,
pos: Position,
shape: Shape,
used_color: &mut Option<&'a ANSIBuf>,
) -> fmt::Result
where
F: Typewriter,
{
let intersection = match cfg.get_intersection(pos, (shape.count_rows, shape.count_columns))
{
Some(c) => c,
None => return Ok(()),
};
if !cfg.has_vertical(pos.col, shape.count_columns) {
return Ok(());
}
let color = cfg.get_intersection_color(pos, (shape.count_rows, shape.count_columns));
prepare_coloring(f, color, used_color)?;
f.write_char(intersection)?;
Ok(())
}
fn prepare_coloring<'a, F>(
f: &mut F,
new_color: Option<&'a ANSIBuf>,
old_color: &mut Option<&'a ANSIBuf>,
) -> fmt::Result
where
F: Typewriter,
{
match new_color {
Some(new_color) => match old_color.as_mut() {
Some(old_color) => {
if **old_color != *new_color {
f.colorize_stop(*old_color)?;
f.colorize_start(new_color)?;
*old_color = new_color;
}
}
None => {
f.colorize_start(new_color)?;
*old_color = Some(new_color);
}
},
None => {
if let Some(color) = old_color.take() {
f.colorize_stop(color)?;
}
}
}
Ok(())
}
fn print_split_line<F, D>(
f: &mut F,
cfg: &SpannedConfig,
dimension: &D,
row: usize,
shape: Shape,
) -> fmt::Result
where
F: Typewriter,
D: Dimension,
{
let mut used_color = None;
print_vertical_intersection(f, cfg, (row, 0).into(), shape, &mut used_color)?;
for col in 0..shape.count_columns {
let width = dimension.get_width(col);
if width > 0 {
let pos = (row, col).into();
let main = cfg.get_horizontal(pos, shape.count_rows);
match main {
Some(c) => {
let clr = cfg.get_horizontal_color(pos, shape.count_rows);
prepare_coloring(f, clr, &mut used_color)?;
print_horizontal_border(f, cfg, pos, width, c, &used_color)?;
}
None => repeat_char(f, ' ', width)?,
}
}
let pos = (row, col + 1).into();
print_vertical_intersection(f, cfg, pos, shape, &mut used_color)?;
}
if let Some(color) = used_color.take() {
f.colorize_stop(color)?;
}
Ok(())
}
fn print_horizontal_border<F>(
f: &mut F,
cfg: &SpannedConfig,
pos: Position,
width: usize,
c: char,
used_color: &Option<&ANSIBuf>,
) -> fmt::Result
where
F: Typewriter,
{
if !cfg.is_overridden_horizontal(pos) {
return repeat_char(f, c, width);
}
for i in 0..width {
let c = cfg.lookup_horizontal_char(pos, i, width).unwrap_or(c);
let color = match cfg.lookup_horizontal_color(pos, i, width) {
Some(color) => color,
None => {
f.write_char(c)?;
continue;
}
};
match used_color {
Some(old_color) => {
f.colorize_stop(old_color)?;
f.colorize_start(color)?;
f.write_char(c)?;
f.colorize_stop(color)?;
f.colorize_start(old_color)?;
}
None => {
f.colorize_start(color)?;
f.write_char(c)?;
f.colorize_stop(color)?;
}
}
}
Ok(())
}
fn print_cell_line<F, R, D, C>(
f: &mut F,
ctx: &PrintCtx<'_, R, D, C>,
width: usize,
height: usize,
pos: Position,
line: usize,
) -> fmt::Result
where
F: Typewriter,
R: Records + PeekableRecords + ExactRecords,
C: Colors,
{
let mut cell_height = ctx.records.count_lines(pos);
let formatting = ctx.cfg.get_formatting(pos);
if formatting.vertical_trim {
cell_height -= count_empty_lines_at_start(ctx.records, pos)
+ count_empty_lines_at_end(ctx.records, pos);
}
if cell_height > height {
cell_height = height;
}
let pad = ctx.cfg.get_padding(pos);
let pad_color = ctx.cfg.get_padding_color(pos);
let alignment = ctx.cfg.get_alignment_vertical(pos);
let indent = top_indent(pad, *alignment, cell_height, height);
if indent > line {
return print_indent(f, pad.top.fill, width, pad_color.top.as_ref());
}
let mut index = line - indent;
let cell_has_this_line = cell_height > index;
if !cell_has_this_line {
return print_indent(f, pad.bottom.fill, width, pad_color.bottom.as_ref());
}
if formatting.vertical_trim {
let empty_lines = count_empty_lines_at_start(ctx.records, pos);
index += empty_lines;
if index > ctx.records.count_lines(pos) {
return print_indent(f, pad.top.fill, width, pad_color.top.as_ref());
}
}
print_indent(f, pad.left.fill, pad.left.size, pad_color.left.as_ref())?;
let width = width - pad.left.size - pad.right.size;
let line_cfg = TextCfg {
alignment: *ctx.cfg.get_alignment_horizontal(pos),
color: ctx.colors.get_color(pos),
justification: Colored::new(
ctx.cfg.get_justification(pos),
ctx.cfg.get_justification_color(pos),
),
formatting,
};
print_line(f, ctx.records, pos, index, width, line_cfg)?;
print_indent(f, pad.right.fill, pad.right.size, pad_color.right.as_ref())?;
Ok(())
}
fn print_line<F, R, C, C1>(
f: &mut F,
records: &R,
pos: Position,
index: usize,
available: usize,
text_cfg: TextCfg<C, C1>,
) -> fmt::Result
where
F: Typewriter,
R: Records + PeekableRecords,
C: ANSIFmt,
C1: ANSIFmt,
{
let line = records.get_line(pos, index);
let (line, line_width) = if text_cfg.formatting.horizontal_trim {
let line = string_trim(line);
let width = get_line_width(&line);
(line, width)
} else {
let width = records.get_line_width(pos, index);
(Cow::Borrowed(line), width)
};
if text_cfg.formatting.allow_lines_alignment {
let indent = calculate_indent(text_cfg.alignment, line_width, available);
let text = Colored::new(line.as_ref(), text_cfg.color);
return print_text_with_pad(f, text, &text_cfg.justification, indent, line_width);
}
let cell_width = if text_cfg.formatting.horizontal_trim {
(0..records.count_lines(pos))
.map(|i| records.get_line(pos, i))
.map(|line| get_line_width(line.trim()))
.max()
.unwrap_or_default()
} else {
records.get_width(pos)
};
let indent = calculate_indent(text_cfg.alignment, cell_width, available);
let text = Colored::new(line.as_ref(), text_cfg.color);
print_text_with_pad(f, text, &text_cfg.justification, indent, line_width)?;
let rest_width = cell_width - line_width;
print_indent(
f,
text_cfg.justification.data,
rest_width,
text_cfg.justification.color,
)?;
Ok(())
}
fn print_text_with_pad<F, C, C1>(
f: &mut F,
text: Colored<&str, C>,
space: &Colored<char, C1>,
indent: HIndent,
width: usize,
) -> fmt::Result
where
F: Typewriter,
C: ANSIFmt,
C1: ANSIFmt,
{
print_indent(f, space.data, indent.left, space.color.as_ref())?;
print_text(f, text.data, text.color, width)?;
print_indent(f, space.data, indent.right, space.color.as_ref())?;
Ok(())
}
fn print_text<F, C>(f: &mut F, text: &str, clr: Option<C>, width: usize) -> fmt::Result
where
F: Typewriter,
C: ANSIFmt,
{
match clr {
Some(color) => {
f.colorize_start(&color)?;
f.write_str(text, width)?;
f.colorize_stop(&color)?;
Ok(())
}
None => f.write_str(text, width),
}
}
fn top_indent(
pad: &Sides<Indent>,
alignment: AlignmentVertical,
cell_height: usize,
available: usize,
) -> usize {
let height = available - pad.top.size;
let indent = indent_from_top(alignment, height, cell_height);
indent + pad.top.size
}
fn indent_from_top(alignment: AlignmentVertical, available: usize, real: usize) -> usize {
match alignment {
AlignmentVertical::Top => 0,
AlignmentVertical::Bottom => available - real,
AlignmentVertical::Center => (available - real) / 2,
}
}
fn calculate_indent(alignment: AlignmentHorizontal, width: usize, available: usize) -> HIndent {
let diff = available - width;
let (left, right) = match alignment {
AlignmentHorizontal::Left => (0, diff),
AlignmentHorizontal::Right => (diff, 0),
AlignmentHorizontal::Center => {
let left = diff / 2;
let rest = diff - left;
(left, rest)
}
};
HIndent { left, right }
}
fn repeat_char<F>(f: &mut F, c: char, n: usize) -> fmt::Result
where
F: Typewriter,
{
for _ in 0..n {
f.write_char(c)?;
}
Ok(())
}
fn count_empty_lines_at_end<R>(records: &R, pos: Position) -> usize
where
R: Records + PeekableRecords,
{
(0..records.count_lines(pos))
.map(|i| records.get_line(pos, i))
.rev()
.take_while(|l| l.trim().is_empty())
.count()
}
fn count_empty_lines_at_start<R>(records: &R, pos: Position) -> usize
where
R: Records + PeekableRecords,
{
(0..records.count_lines(pos))
.map(|i| records.get_line(pos, i))
.take_while(|s| s.trim().is_empty())
.count()
}
fn total_width<D>(cfg: &SpannedConfig, dimension: &D, count_columns: usize) -> usize
where
D: Dimension,
{
(0..count_columns)
.map(|i| dimension.get_width(i))
.sum::<usize>()
+ cfg.count_vertical(count_columns)
}
fn total_height<D>(cfg: &SpannedConfig, dimension: &D, count_rows: usize) -> usize
where
D: Dimension,
{
(0..count_rows)
.map(|i| dimension.get_height(i))
.sum::<usize>()
+ cfg.count_horizontal(count_rows)
}
fn print_margin_top<F>(f: &mut F, cfg: &SpannedConfig, width: usize) -> fmt::Result
where
F: Typewriter,
{
let indent = cfg.get_margin().top;
let offset = cfg.get_margin_offset().top;
let color = cfg.get_margin_color();
let color = color.top;
print_indent_lines(f, &indent, &offset, color, width)
}
fn print_margin_bottom<F>(f: &mut F, cfg: &SpannedConfig, width: usize) -> fmt::Result
where
F: Typewriter,
{
let indent = cfg.get_margin().bottom;
let offset = cfg.get_margin_offset().bottom;
let color = cfg.get_margin_color();
let color = color.bottom;
print_indent_lines(f, &indent, &offset, color, width)
}
fn print_margin_left<F>(
f: &mut F,
cfg: &SpannedConfig,
line: usize,
height: usize,
) -> fmt::Result
where
F: Typewriter,
{
let indent = cfg.get_margin().left;
let offset = cfg.get_margin_offset().left;
let color = cfg.get_margin_color();
let color = color.left;
print_margin_vertical(f, indent, offset, color, line, height)
}
fn print_margin_right<F>(
f: &mut F,
cfg: &SpannedConfig,
line: usize,
height: usize,
) -> fmt::Result
where
F: Typewriter,
{
let indent = cfg.get_margin().right;
let offset = cfg.get_margin_offset().right;
let color = cfg.get_margin_color();
let color = color.right;
print_margin_vertical(f, indent, offset, color, line, height)
}
fn print_margin_vertical<F>(
f: &mut F,
indent: Indent,
offset: Offset,
color: Option<&ANSIBuf>,
line: usize,
height: usize,
) -> fmt::Result
where
F: Typewriter,
{
if indent.size == 0 {
return Ok(());
}
match offset {
Offset::Start(offset) => {
let offset = cmp::min(offset, height);
if line >= offset {
print_indent(f, indent.fill, indent.size, color)?;
} else {
repeat_char(f, ' ', indent.size)?;
}
}
Offset::End(offset) => {
let offset = cmp::min(offset, height);
let pos = height - offset;
if line >= pos {
repeat_char(f, ' ', indent.size)?;
} else {
print_indent(f, indent.fill, indent.size, color)?;
}
}
}
Ok(())
}
fn print_indent_lines<F>(
f: &mut F,
indent: &Indent,
offset: &Offset,
color: Option<&ANSIBuf>,
width: usize,
) -> fmt::Result
where
F: Typewriter,
{
if indent.size == 0 {
return Ok(());
}
let (start_offset, end_offset) = match offset {
Offset::Start(start) => (*start, 0),
Offset::End(end) => (0, *end),
};
let start_offset = std::cmp::min(start_offset, width);
let end_offset = std::cmp::min(end_offset, width);
let indent_size = width - start_offset - end_offset;
for i in 0..indent.size {
if start_offset > 0 {
repeat_char(f, ' ', start_offset)?;
}
if indent_size > 0 {
print_indent(f, indent.fill, indent_size, color)?;
}
if end_offset > 0 {
repeat_char(f, ' ', end_offset)?;
}
if i + 1 != indent.size {
f.write_char('\n')?;
}
}
Ok(())
}
fn print_indent<F, C>(f: &mut F, c: char, n: usize, color: Option<C>) -> fmt::Result
where
F: Typewriter,
C: ANSIFmt,
{
if n == 0 {
return Ok(());
}
match color {
Some(color) => {
f.colorize_start(&color)?;
repeat_char(f, c, n)?;
f.colorize_stop(&color)?;
Ok(())
}
None => repeat_char(f, c, n),
}
}
fn get_cell_width<D>(cfg: &SpannedConfig, dims: &D, pos: Position, max: usize) -> usize
where
D: Dimension,
{
match cfg.get_column_span(pos) {
Some(span) => {
let start = pos.col;
let end = start + span;
range_width(dims, start, end) + count_verticals_range(cfg, start, end, max)
}
None => dims.get_width(pos.col),
}
}
fn range_width<D>(dims: &D, start: usize, end: usize) -> usize
where
D: Dimension,
{
(start..end).map(|col| dims.get_width(col)).sum::<usize>()
}
fn count_verticals_range(cfg: &SpannedConfig, start: usize, end: usize, max: usize) -> usize {
(start + 1..end)
.map(|i| cfg.has_vertical(i, max) as usize)
.sum()
}
fn get_cell_height<D>(cfg: &SpannedConfig, dims: &D, pos: Position, max: usize) -> usize
where
D: Dimension,
{
match cfg.get_row_span(pos) {
Some(span) => {
let start = pos.row;
let end = pos.row + span;
range_height(dims, start, end) + count_horizontals_range(cfg, start, end, max)
}
None => dims.get_height(pos.row),
}
}
fn range_height<D>(dims: &D, start: usize, end: usize) -> usize
where
D: Dimension,
{
(start..end).map(|col| dims.get_height(col)).sum::<usize>()
}
fn count_horizontals_range(cfg: &SpannedConfig, start: usize, end: usize, max: usize) -> usize {
(start + 1..end)
.map(|i| cfg.has_horizontal(i, max) as usize)
.sum()
}
fn closest_visible_row(cfg: &SpannedConfig, mut pos: Position) -> Option<usize> {
loop {
if cfg.is_cell_visible(pos) {
return Some(pos.row);
}
if pos.row == 0 {
return None;
}
pos -= (1, 0);
}
}
fn string_trim(text: &str) -> Cow<'_, str> {
#[cfg(feature = "ansi")]
{
ansi_str::AnsiStr::ansi_trim(text)
}
#[cfg(not(feature = "ansi"))]
{
text.trim().into()
}
}
}