1//! This module contains a [`Highlight`] primitive, which helps
2//! changing a [`Border`] style of any segment on a [`Table`].
3//!
4//! [`Table`]: crate::Table
5
6use std::collections::HashSet;
7
8use crate::{
9 grid::{
10 ansi::ANSIBuf,
11 config::{Border as GridBorder, ColoredConfig, Entity, Position, SpannedConfig},
12 records::{ExactRecords, Records},
13 },
14 settings::{object::Object, style::BorderColor, Border, Color, TableOption},
15};
16
17/// Highlight modifies a table style by changing a border of a target [`Table`] segment.
18///
19/// # Example
20///
21/// ```
22/// use tabled::{
23/// Table,
24/// settings::{Highlight, Border, Style, object::Segment}
25/// };
26///
27/// let data = [
28/// ("ELF", "Extensible Linking Format", true),
29/// ("DWARF", "", true),
30/// ("PE", "Portable Executable", false),
31/// ];
32///
33/// let table = Table::new(data.iter().enumerate())
34/// .with(Style::markdown())
35/// .with(Highlight::border(Segment::all(), Border::new().set_top('^').set_bottom('v')))
36/// .to_string();
37///
38/// assert_eq!(
39/// table,
40/// concat!(
41/// " ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ \n",
42/// "| usize | &str | &str | bool |\n",
43/// "|-------|-------|---------------------------|-------|\n",
44/// "| 0 | ELF | Extensible Linking Format | true |\n",
45/// "| 1 | DWARF | | true |\n",
46/// "| 2 | PE | Portable Executable | false |\n",
47/// " vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv ",
48/// ),
49/// );
50/// ```
51///
52/// It's possible to use [`Highlight`] for many kinds of figures.
53///
54/// ```
55/// use tabled::{
56/// Table,
57/// settings::{
58/// Highlight, Border, Style,
59/// object::{Segment, Object}
60/// }
61/// };
62///
63/// let data = [
64/// ("ELF", "Extensible Linking Format", true),
65/// ("DWARF", "", true),
66/// ("PE", "Portable Executable", false),
67/// ];
68///
69/// let table = Table::new(data.iter().enumerate())
70/// .with(Style::markdown())
71/// .with(Highlight::border(Segment::all().not((0,0).and((1, 0)).and((0, 1)).and((0, 3))), Border::filled('*')))
72/// .to_string();
73///
74/// println!("{}", table);
75///
76/// assert_eq!(
77/// table,
78/// concat!(
79/// " ***************************** \n",
80/// "| usize | &str * &str * bool |\n",
81/// "|-------*********---------------------------*********\n",
82/// "| 0 * ELF | Extensible Linking Format | true *\n",
83/// "********* *\n",
84/// "* 1 | DWARF | | true *\n",
85/// "* *\n",
86/// "* 2 | PE | Portable Executable | false *\n",
87/// "*****************************************************",
88/// ),
89/// );
90/// ```
91///
92/// [`Table`]: crate::Table
93#[derive(Debug)]
94pub struct Highlight<O> {
95 target: O,
96 border: HighlightInner,
97}
98
99#[derive(Debug)]
100enum HighlightInner {
101 Border(GridBorder<char>),
102 // #[cfg(feature = "ansi")]
103 Color(GridBorder<ANSIBuf>),
104 // #[cfg(feature = "ansi")]
105 ColoredBorder(GridBorder<char>, GridBorder<ANSIBuf>),
106}
107
108// todo: Docs testss
109
110// todo: Move colors to Highlight itself
111// todo: Maybe add a default Border and set ask for a char -> Highlight::new(target).border().outline().color().outline_color()
112
113// todo: Add BorderColor.
114
115impl<O> Highlight<O> {
116 /// Build a new instance of [`Highlight`]
117 ///
118 /// BE AWARE: if target exceeds boundaries it may panic.
119 pub const fn border<T, B, L, R>(target: O, border: Border<T, B, L, R>) -> Self {
120 Self {
121 target,
122 border: HighlightInner::Border(border.into_inner()),
123 }
124 }
125
126 /// Build a new instance of [`Highlight`]
127 ///
128 /// BE AWARE: if target exceeds boundaries it may panic.
129 // #[cfg(feature = "ansi")]
130 pub fn color<T, B, L, R>(target: O, border: BorderColor<T, B, L, R>) -> Self {
131 let color = border.into_inner();
132 let color = color.convert();
133
134 Self {
135 target,
136 border: HighlightInner::Color(color),
137 }
138 }
139
140 /// Build a new instance of [`Highlight`]
141 ///
142 /// BE AWARE: if target exceeds boundaries it may panic.
143 pub const fn outline(target: O, c: char) -> Self {
144 Self::border(target, Border::filled(c))
145 }
146
147 /// Build a new instance of [`Highlight`]
148 ///
149 /// BE AWARE: if target exceeds boundaries it may panic.
150 pub fn colored_outline<T, B, L, R>(target: O, c: char, color: Color) -> Self {
151 Self::colored_border(target, Border::filled(c), BorderColor::filled(color))
152 }
153
154 /// Build a new instance of [`Highlight`]
155 ///
156 /// BE AWARE: if target exceeds boundaries it may panic.
157 // #[cfg(feature = "ansi")]
158 pub fn colored_border<T, B, L, R>(
159 target: O,
160 border: Border<T, B, L, R>,
161 color: BorderColor<T, B, L, R>,
162 ) -> Self {
163 let border = border.into_inner();
164 let color = color.into_inner().convert();
165
166 Self {
167 target,
168 border: HighlightInner::ColoredBorder(border, color),
169 }
170 }
171}
172
173impl<O, R, D> TableOption<R, ColoredConfig, D> for Highlight<O>
174where
175 O: Object<R>,
176 R: Records + ExactRecords,
177{
178 fn change(self, records: &mut R, cfg: &mut ColoredConfig, _: &mut D) {
179 let count_rows = records.count_rows();
180 let count_cols = records.count_columns();
181
182 let cells = self.target.cells(records);
183 let segments = split_segments(cells, count_rows, count_cols);
184
185 match self.border {
186 HighlightInner::Border(border) => {
187 for sector in segments {
188 set_border(cfg, &sector, border);
189 }
190 }
191 HighlightInner::Color(color) => {
192 for sector in segments {
193 set_border_color(cfg, &sector, &color);
194 }
195 }
196 HighlightInner::ColoredBorder(border, color) => {
197 for sector in segments {
198 set_border(cfg, &sector, border);
199 set_border_color(cfg, &sector, &color);
200 }
201 }
202 }
203 }
204
205 fn hint_change(&self) -> Option<Entity> {
206 None
207 }
208}
209
210fn set_border_color(
211 cfg: &mut SpannedConfig,
212 sector: &HashSet<(usize, usize)>,
213 border: &GridBorder<ANSIBuf>,
214) {
215 if sector.is_empty() {
216 return;
217 }
218 let color: Border = border.clone();
219 for &(row: usize, col: usize) in sector {
220 let border: Border = build_cell_border(sector, (row, col), &color);
221 cfg.set_border_color((row, col), border);
222 }
223}
224
225fn split_segments(
226 cells: impl Iterator<Item = Entity>,
227 count_rows: usize,
228 count_cols: usize,
229) -> Vec<HashSet<(usize, usize)>> {
230 let mut segments: Vec<HashSet<(usize, usize)>> = Vec::new();
231 for entity in cells {
232 for cell in entity.iter(count_rows, count_cols) {
233 let found_segment = segments
234 .iter_mut()
235 .find(|s| s.iter().any(|&c| is_cell_connected(cell, c)));
236
237 match found_segment {
238 Some(segment) => {
239 let _ = segment.insert(cell);
240 }
241 None => {
242 let mut segment = HashSet::new();
243 let _ = segment.insert(cell);
244 segments.push(segment);
245 }
246 }
247 }
248 }
249
250 let mut squashed_segments: Vec<HashSet<(usize, usize)>> = Vec::new();
251 while !segments.is_empty() {
252 let mut segment = segments.remove(0);
253
254 let mut i = 0;
255 while i < segments.len() {
256 if is_segment_connected(&segment, &segments[i]) {
257 segment.extend(&segments[i]);
258 let _ = segments.remove(i);
259 } else {
260 i += 1;
261 }
262 }
263
264 squashed_segments.push(segment);
265 }
266
267 squashed_segments
268}
269
270fn is_cell_connected((row1: usize, col1: usize): (usize, usize), (row2: usize, col2: usize): (usize, usize)) -> bool {
271 if col1 == col2 && row1 == row2 + 1 {
272 return true;
273 }
274
275 if col1 == col2 && (row2 > 0 && row1 == row2 - 1) {
276 return true;
277 }
278
279 if row1 == row2 && col1 == col2 + 1 {
280 return true;
281 }
282
283 if row1 == row2 && (col2 > 0 && col1 == col2 - 1) {
284 return true;
285 }
286
287 false
288}
289
290fn is_segment_connected(
291 segment1: &HashSet<(usize, usize)>,
292 segment2: &HashSet<(usize, usize)>,
293) -> bool {
294 for &cell1: (usize, usize) in segment1.iter() {
295 for &cell2: (usize, usize) in segment2.iter() {
296 if is_cell_connected(cell1, cell2) {
297 return true;
298 }
299 }
300 }
301
302 false
303}
304
305fn set_border(cfg: &mut SpannedConfig, sector: &HashSet<(usize, usize)>, border: GridBorder<char>) {
306 if sector.is_empty() {
307 return;
308 }
309
310 for &pos: (usize, usize) in sector {
311 let border: Border = build_cell_border(sector, pos, &border);
312 cfg.set_border(pos, border);
313 }
314}
315
316fn build_cell_border<T>(
317 sector: &HashSet<(usize, usize)>,
318 (row: usize, col: usize): Position,
319 border: &GridBorder<T>,
320) -> GridBorder<T>
321where
322 T: Default + Clone,
323{
324 let cell_has_top_neighbor = cell_has_top_neighbor(sector, row, col);
325 let cell_has_bottom_neighbor = cell_has_bottom_neighbor(sector, row, col);
326 let cell_has_left_neighbor = cell_has_left_neighbor(sector, row, col);
327 let cell_has_right_neighbor = cell_has_right_neighbor(sector, row, col);
328
329 let this_has_left_top_neighbor = is_there_left_top_cell(sector, row, col);
330 let this_has_right_top_neighbor = is_there_right_top_cell(sector, row, col);
331 let this_has_left_bottom_neighbor = is_there_left_bottom_cell(sector, row, col);
332 let this_has_right_bottom_neighbor = is_there_right_bottom_cell(sector, row, col);
333
334 let mut cell_border = GridBorder::default();
335 if let Some(c) = border.top.clone() {
336 if !cell_has_top_neighbor {
337 cell_border.top = Some(c.clone());
338
339 if cell_has_right_neighbor && !this_has_right_top_neighbor {
340 cell_border.right_top_corner = Some(c);
341 }
342 }
343 }
344 if let Some(c) = border.bottom.clone() {
345 if !cell_has_bottom_neighbor {
346 cell_border.bottom = Some(c.clone());
347
348 if cell_has_right_neighbor && !this_has_right_bottom_neighbor {
349 cell_border.right_bottom_corner = Some(c);
350 }
351 }
352 }
353 if let Some(c) = border.left.clone() {
354 if !cell_has_left_neighbor {
355 cell_border.left = Some(c.clone());
356
357 if cell_has_bottom_neighbor && !this_has_left_bottom_neighbor {
358 cell_border.left_bottom_corner = Some(c);
359 }
360 }
361 }
362 if let Some(c) = border.right.clone() {
363 if !cell_has_right_neighbor {
364 cell_border.right = Some(c.clone());
365
366 if cell_has_bottom_neighbor && !this_has_right_bottom_neighbor {
367 cell_border.right_bottom_corner = Some(c);
368 }
369 }
370 }
371 if let Some(c) = border.left_top_corner.clone() {
372 if !cell_has_left_neighbor && !cell_has_top_neighbor {
373 cell_border.left_top_corner = Some(c);
374 }
375 }
376 if let Some(c) = border.left_bottom_corner.clone() {
377 if !cell_has_left_neighbor && !cell_has_bottom_neighbor {
378 cell_border.left_bottom_corner = Some(c);
379 }
380 }
381 if let Some(c) = border.right_top_corner.clone() {
382 if !cell_has_right_neighbor && !cell_has_top_neighbor {
383 cell_border.right_top_corner = Some(c);
384 }
385 }
386 if let Some(c) = border.right_bottom_corner.clone() {
387 if !cell_has_right_neighbor && !cell_has_bottom_neighbor {
388 cell_border.right_bottom_corner = Some(c);
389 }
390 }
391 {
392 if !cell_has_bottom_neighbor {
393 if !cell_has_left_neighbor && this_has_left_top_neighbor {
394 if let Some(c) = border.right_top_corner.clone() {
395 cell_border.left_top_corner = Some(c);
396 }
397 }
398
399 if cell_has_left_neighbor && this_has_left_bottom_neighbor {
400 if let Some(c) = border.left_top_corner.clone() {
401 cell_border.left_bottom_corner = Some(c);
402 }
403 }
404
405 if !cell_has_right_neighbor && this_has_right_top_neighbor {
406 if let Some(c) = border.left_top_corner.clone() {
407 cell_border.right_top_corner = Some(c);
408 }
409 }
410
411 if cell_has_right_neighbor && this_has_right_bottom_neighbor {
412 if let Some(c) = border.right_top_corner.clone() {
413 cell_border.right_bottom_corner = Some(c);
414 }
415 }
416 }
417
418 if !cell_has_top_neighbor {
419 if !cell_has_left_neighbor && this_has_left_bottom_neighbor {
420 if let Some(c) = border.right_bottom_corner.clone() {
421 cell_border.left_bottom_corner = Some(c);
422 }
423 }
424
425 if cell_has_left_neighbor && this_has_left_top_neighbor {
426 if let Some(c) = border.left_bottom_corner.clone() {
427 cell_border.left_top_corner = Some(c);
428 }
429 }
430
431 if !cell_has_right_neighbor && this_has_right_bottom_neighbor {
432 if let Some(c) = border.left_bottom_corner.clone() {
433 cell_border.right_bottom_corner = Some(c);
434 }
435 }
436
437 if cell_has_right_neighbor && this_has_right_top_neighbor {
438 if let Some(c) = border.right_bottom_corner.clone() {
439 cell_border.right_top_corner = Some(c);
440 }
441 }
442 }
443 }
444
445 cell_border
446}
447
448fn cell_has_top_neighbor(sector: &HashSet<(usize, usize)>, row: usize, col: usize) -> bool {
449 row > 0 && sector.contains(&(row - 1, col))
450}
451
452fn cell_has_bottom_neighbor(sector: &HashSet<(usize, usize)>, row: usize, col: usize) -> bool {
453 sector.contains(&(row + 1, col))
454}
455
456fn cell_has_left_neighbor(sector: &HashSet<(usize, usize)>, row: usize, col: usize) -> bool {
457 col > 0 && sector.contains(&(row, col - 1))
458}
459
460fn cell_has_right_neighbor(sector: &HashSet<(usize, usize)>, row: usize, col: usize) -> bool {
461 sector.contains(&(row, col + 1))
462}
463
464fn is_there_left_top_cell(sector: &HashSet<(usize, usize)>, row: usize, col: usize) -> bool {
465 row > 0 && col > 0 && sector.contains(&(row - 1, col - 1))
466}
467
468fn is_there_right_top_cell(sector: &HashSet<(usize, usize)>, row: usize, col: usize) -> bool {
469 row > 0 && sector.contains(&(row - 1, col + 1))
470}
471
472fn is_there_left_bottom_cell(sector: &HashSet<(usize, usize)>, row: usize, col: usize) -> bool {
473 col > 0 && sector.contains(&(row + 1, col - 1))
474}
475
476fn is_there_right_bottom_cell(sector: &HashSet<(usize, usize)>, row: usize, col: usize) -> bool {
477 sector.contains(&(row + 1, col + 1))
478}
479
480// fn split_tuple_border<A, B>(border: GridBorder<(A, B)>) -> (GridBorder<A>, GridBorder<B>) {
481// macro_rules! set_border {
482// ($border1:expr, $border2:expr => $symbl:expr) => {
483// if let Some((x1, x2)) = $symbl {
484// $border1 = Some(x1);
485// $border2 = Some(x2);
486// }
487// };
488// }
489
490// let mut a = GridBorder::empty();
491// let mut b = GridBorder::empty();
492
493// set_border!(a.bottom, b.bottom => border.bottom);
494// set_border!(a.top, b.top => border.top);
495// set_border!(a.left, b.left => border.left);
496// set_border!(a.left_top_corner, b.left_top_corner => border.left_top_corner);
497// set_border!(a.left_bottom_corner, b.left_bottom_corner => border.left_bottom_corner);
498// set_border!(a.right, b.right => border.right);
499// set_border!(a.right_top_corner, b.right_top_corner => border.right_top_corner);
500// set_border!(a.right_bottom_corner, b.right_bottom_corner => border.right_bottom_corner);
501
502// (a, b)
503// }
504
505#[cfg(test)]
506mod tests {
507 use super::*;
508
509 #[test]
510 fn test_is_connected() {
511 assert!(is_cell_connected((0, 0), (0, 1)));
512 assert!(is_cell_connected((0, 0), (1, 0)));
513 assert!(!is_cell_connected((0, 0), (1, 1)));
514
515 assert!(is_cell_connected((0, 1), (0, 0)));
516 assert!(is_cell_connected((1, 0), (0, 0)));
517 assert!(!is_cell_connected((1, 1), (0, 0)));
518
519 assert!(is_cell_connected((1, 1), (0, 1)));
520 assert!(is_cell_connected((1, 1), (1, 0)));
521 assert!(is_cell_connected((1, 1), (2, 1)));
522 assert!(is_cell_connected((1, 1), (1, 2)));
523 assert!(!is_cell_connected((1, 1), (1, 1)));
524 }
525}
526