Skip to main content

lexa_logger/layout/
cell.rs

1// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
2// ┃ Copyright: (c) 2023, Mike 'PhiSyX' S. (https://github.com/PhiSyX)         ┃
3// ┃ SPDX-License-Identifier: MPL-2.0                                          ┃
4// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
5// ┃                                                                           ┃
6// ┃  This Source Code Form is subject to the terms of the Mozilla Public      ┃
7// ┃  License, v. 2.0. If a copy of the MPL was not distributed with this      ┃
8// ┃  file, You can obtain one at https://mozilla.org/MPL/2.0/.                ┃
9// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
10
11use std::borrow::Cow;
12use std::cmp;
13use std::collections::HashSet;
14
15use unicode_width::UnicodeWidthStr;
16
17use super::style::Alignment;
18
19// --------- //
20// Structure //
21// --------- //
22
23#[derive(Debug)]
24#[derive(Clone)]
25pub struct Cell<'d>
26{
27	pub(crate) alignment: Alignment,
28	pub(crate) colspan: usize,
29	data: Cow<'d, str>,
30	padding: bool,
31}
32
33// -------------- //
34// Implémentation //
35// -------------- //
36
37impl<'d> Cell<'d>
38{
39	pub fn new(data: impl ToString) -> Self
40	{
41		Self {
42			alignment: Alignment::Left,
43			colspan: 1,
44			data: Cow::from(data.to_string()),
45			padding: true,
46		}
47	}
48
49	pub fn with_alignment(mut self, alignment: Alignment) -> Self
50	{
51		self.alignment = alignment;
52		self
53	}
54}
55
56impl<'d> Cell<'d>
57{
58	pub(crate) fn width(&self) -> usize
59	{
60		self.wrapped_content(std::usize::MAX)
61			.iter()
62			.fold(0, |max, text| cmp::max(max, str_len(text)))
63	}
64
65	pub(crate) fn split_width(&self) -> f32
66	{
67		self.width() as f32 / self.colspan as f32
68	}
69
70	pub(crate) fn min_width(&self) -> usize
71	{
72		let max_char_width = self.data.chars().fold(0, |max, ch| cmp::max(max, ch.len_utf8()));
73
74		if self.padding {
75			max_char_width + ' '.len_utf8() * 2
76		} else {
77			max_char_width
78		}
79	}
80
81	pub(crate) fn wrapped_content(&self, width: usize) -> Vec<String>
82	{
83		let pad_char = if self.padding { ' ' } else { '\0' };
84
85		let hidden: HashSet<usize> = STRIP_ANSI_RE
86			.find_iter(&self.data)
87			.flat_map(|m| m.start()..m.end())
88			.collect();
89
90		let mut output = Vec::default();
91
92		let mut temporary_buffer = String::default();
93
94		temporary_buffer.push(pad_char);
95
96		let mut byte_index = 0;
97
98		for ch in self.data.chars() {
99			if !hidden.contains(&byte_index)
100				&& (str_len(&temporary_buffer) >= width - pad_char.len_utf8() || ch == '\n')
101			{
102				temporary_buffer.push(pad_char);
103				output.push(temporary_buffer);
104				temporary_buffer = String::new();
105				temporary_buffer.push(pad_char);
106
107				if ch == '\n' {
108					byte_index += 1;
109					continue;
110				}
111			}
112
113			byte_index += ch.len_utf8();
114
115			temporary_buffer.push(ch);
116		}
117
118		temporary_buffer.push(pad_char);
119
120		output.push(temporary_buffer);
121
122		output
123	}
124}
125
126pub(crate) fn str_len(string: &str) -> usize
127{
128	let stripped = STRIP_ANSI_RE.replace_all(string, "");
129	stripped.width()
130}
131
132lazy_static::lazy_static! {
133	// STRIP_ANSI_RE is lifted from the "console" crate.
134	// Copyright 2017 Armin Ronacher <armin.ronacher@active-4.com>. MIT License.
135	static ref STRIP_ANSI_RE: regex::Regex = regex::Regex::new(
136		r"[\x1b\x9b][\[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-PRZcf-nqry=><]"
137	)
138	.unwrap();
139}