tabled/settings/highlight/
mod.rs1use std::collections::HashSet;
7
8use crate::{
9 grid::{
10 ansi::ANSIBuf,
11 config::{Border, ColoredConfig, Entity, Position, SpannedConfig},
12 records::{ExactRecords, Records},
13 },
14 settings::{
15 object::Object,
16 style::{Border as ConstBorder, BorderColor},
17 Color, TableOption,
18 },
19};
20
21#[derive(Debug)]
65pub struct Highlight<O> {
66 target: O,
67 border: Option<Border<char>>,
68 color: Option<Border<ANSIBuf>>,
69}
70
71impl<O> Highlight<O> {
72 pub const fn new(target: O) -> Self {
76 Self::_new(target, None, None)
77 }
78
79 pub const fn outline(target: O, c: char) -> Self {
84 Self::_new(target, Some(Border::filled(c)), None)
85 }
86
87 pub fn colored(target: O, color: Color) -> Self {
92 let color = Border::filled(&color).cloned().convert();
93 Self::_new(target, None, Some(color))
94 }
95
96 pub fn border<T, B, L, R>(self, border: ConstBorder<T, B, L, R>) -> Self {
98 let border = border.into_inner();
99 Self {
100 target: self.target,
101 border: Some(border),
102 color: self.color,
103 }
104 }
105
106 pub fn color(self, border: BorderColor) -> Self {
108 let border = border.into_inner();
109 let border = border.convert();
110
111 Self {
112 target: self.target,
113 border: self.border,
114 color: Some(border),
115 }
116 }
117
118 const fn _new(target: O, border: Option<Border<char>>, color: Option<Border<ANSIBuf>>) -> Self {
122 Self {
123 target,
124 border,
125 color,
126 }
127 }
128}
129
130impl<O, R, D> TableOption<R, ColoredConfig, D> for Highlight<O>
131where
132 O: Object<R>,
133 R: Records + ExactRecords,
134{
135 fn change(self, records: &mut R, cfg: &mut ColoredConfig, _: &mut D) {
136 let count_rows = records.count_rows();
137 let count_cols = records.count_columns();
138
139 let cells = self.target.cells(records);
140 let segments = split_segments(cells, count_rows, count_cols);
141
142 match (self.border, self.color) {
143 (None, Some(color)) => {
144 for sector in segments {
145 set_border_color(cfg, §or, &color);
146 }
147 }
148 (Some(border), None) => {
149 for sector in segments {
150 set_border(cfg, §or, border);
151 }
152 }
153 (Some(border), Some(color)) => {
154 for sector in segments {
155 set_border(cfg, §or, border);
156 set_border_color(cfg, §or, &color);
157 }
158 }
159 (None, None) => {
160 }
162 }
163 }
164
165 fn hint_change(&self) -> Option<Entity> {
166 None
167 }
168}
169
170fn set_border_color(cfg: &mut SpannedConfig, sector: &HashSet<Position>, border: &Border<ANSIBuf>) {
171 if sector.is_empty() {
172 return;
173 }
174 let color = border.clone();
175 for &p in sector {
176 let border = build_cell_border(sector, p, &color);
177 cfg.set_border_color(p, border);
178 }
179}
180
181fn split_segments(
182 cells: impl Iterator<Item = Entity>,
183 count_rows: usize,
184 count_cols: usize,
185) -> Vec<HashSet<Position>> {
186 let mut segments: Vec<HashSet<Position>> = Vec::new();
187 for entity in cells {
188 for cell in entity.iter(count_rows, count_cols) {
189 let found_segment = segments
190 .iter_mut()
191 .find(|s| s.iter().any(|&c| is_cell_connected(cell, c)));
192
193 match found_segment {
194 Some(segment) => {
195 let _ = segment.insert(cell);
196 }
197 None => {
198 let mut segment = HashSet::new();
199 let _ = segment.insert(cell);
200 segments.push(segment);
201 }
202 }
203 }
204 }
205
206 let mut squashed_segments: Vec<HashSet<Position>> = Vec::new();
207 while !segments.is_empty() {
208 let mut segment = segments.remove(0);
209
210 let mut i = 0;
211 while i < segments.len() {
212 if is_segment_connected(&segment, &segments[i]) {
213 segment.extend(&segments[i]);
214 let _ = segments.remove(i);
215 } else {
216 i += 1;
217 }
218 }
219
220 squashed_segments.push(segment);
221 }
222
223 squashed_segments
224}
225
226fn is_cell_connected(p1: Position, p2: Position) -> bool {
227 if p1.col == p2.col {
228 let up = p1.row == p2.row + 1;
229 let down = p2.row > 0 && p1.row == p2.row - 1;
230
231 if up || down {
232 return true;
233 }
234 }
235
236 if p1.row == p2.row {
237 let left = p2.col > 0 && p1.col == p2.col - 1;
238 let right = p1.col == p2.col + 1;
239
240 if left || right {
241 return true;
242 }
243 }
244
245 false
246}
247
248fn is_segment_connected(segment1: &HashSet<Position>, segment2: &HashSet<Position>) -> bool {
249 for &cell1 in segment1.iter() {
250 for &cell2 in segment2.iter() {
251 if is_cell_connected(cell1, cell2) {
252 return true;
253 }
254 }
255 }
256
257 false
258}
259
260fn set_border(cfg: &mut SpannedConfig, sector: &HashSet<Position>, border: Border<char>) {
261 if sector.is_empty() {
262 return;
263 }
264
265 for &pos in sector {
266 let border = build_cell_border(sector, pos, &border);
267 cfg.set_border(pos, border);
268 }
269}
270
271fn build_cell_border<T>(sector: &HashSet<Position>, p: Position, border: &Border<T>) -> Border<T>
272where
273 T: Default + Clone,
274{
275 let has_top_neighbor = has_top_neighbor(sector, p);
276 let has_bottom_neighbor = has_bottom_neighbor(sector, p);
277 let has_left_neighbor = has_left_neighbor(sector, p);
278 let has_right_neighbor = has_right_neighbor(sector, p);
279 let has_left_top_neighbor = has_left_top_neighbor(sector, p);
280 let has_right_top_neighbor = has_right_top_neighbor(sector, p);
281 let has_left_bottom_neighbor = has_left_bottom_neighbor(sector, p);
282 let has_right_bottom_neighbor = has_right_bottom_neighbor(sector, p);
283
284 let mut b = Border::default();
285
286 if let Some(c) = border.top.clone() {
287 if !has_top_neighbor {
288 b.top = Some(c.clone());
289
290 if has_right_neighbor && !has_right_top_neighbor {
291 b.right_top_corner = Some(c);
292 }
293 }
294 }
295
296 if let Some(c) = border.bottom.clone() {
297 if !has_bottom_neighbor {
298 b.bottom = Some(c.clone());
299
300 if has_right_neighbor && !has_right_bottom_neighbor {
301 b.right_bottom_corner = Some(c);
302 }
303 }
304 }
305
306 if let Some(c) = border.left.clone() {
307 if !has_left_neighbor {
308 b.left = Some(c.clone());
309
310 if has_bottom_neighbor && !has_left_bottom_neighbor {
311 b.left_bottom_corner = Some(c);
312 }
313 }
314 }
315
316 if let Some(c) = border.right.clone() {
317 if !has_right_neighbor {
318 b.right = Some(c.clone());
319
320 if has_bottom_neighbor && !has_right_bottom_neighbor {
321 b.right_bottom_corner = Some(c);
322 }
323 }
324 }
325
326 if let Some(c) = border.left_top_corner.clone() {
327 if !has_left_neighbor && !has_top_neighbor {
328 b.left_top_corner = Some(c);
329 }
330 }
331
332 if let Some(c) = border.left_bottom_corner.clone() {
333 if !has_left_neighbor && !has_bottom_neighbor {
334 b.left_bottom_corner = Some(c);
335 }
336 }
337
338 if let Some(c) = border.right_top_corner.clone() {
339 if !has_right_neighbor && !has_top_neighbor {
340 b.right_top_corner = Some(c);
341 }
342 }
343
344 if let Some(c) = border.right_bottom_corner.clone() {
345 if !has_right_neighbor && !has_bottom_neighbor {
346 b.right_bottom_corner = Some(c);
347 }
348 }
349
350 {
351 if !has_bottom_neighbor {
352 if !has_left_neighbor && has_left_top_neighbor {
353 if let Some(c) = border.right_top_corner.clone() {
354 b.left_top_corner = Some(c);
355 }
356 }
357
358 if has_left_neighbor && has_left_bottom_neighbor {
359 if let Some(c) = border.left_top_corner.clone() {
360 b.left_bottom_corner = Some(c);
361 }
362 }
363
364 if !has_right_neighbor && has_right_top_neighbor {
365 if let Some(c) = border.left_top_corner.clone() {
366 b.right_top_corner = Some(c);
367 }
368 }
369
370 if has_right_neighbor && has_right_bottom_neighbor {
371 if let Some(c) = border.right_top_corner.clone() {
372 b.right_bottom_corner = Some(c);
373 }
374 }
375 }
376
377 if !has_top_neighbor {
378 if !has_left_neighbor && has_left_bottom_neighbor {
379 if let Some(c) = border.right_bottom_corner.clone() {
380 b.left_bottom_corner = Some(c);
381 }
382 }
383
384 if has_left_neighbor && has_left_top_neighbor {
385 if let Some(c) = border.left_bottom_corner.clone() {
386 b.left_top_corner = Some(c);
387 }
388 }
389
390 if !has_right_neighbor && has_right_bottom_neighbor {
391 if let Some(c) = border.left_bottom_corner.clone() {
392 b.right_bottom_corner = Some(c);
393 }
394 }
395
396 if has_right_neighbor && has_right_top_neighbor {
397 if let Some(c) = border.right_bottom_corner.clone() {
398 b.right_top_corner = Some(c);
399 }
400 }
401 }
402 }
403
404 b
405}
406
407fn has_top_neighbor(sector: &HashSet<Position>, p: Position) -> bool {
408 p.row > 0 && sector.contains(&(p - (1, 0)))
409}
410
411fn has_bottom_neighbor(sector: &HashSet<Position>, p: Position) -> bool {
412 sector.contains(&(p + (1, 0)))
413}
414
415fn has_left_neighbor(sector: &HashSet<Position>, p: Position) -> bool {
416 p.col > 0 && sector.contains(&(p - (0, 1)))
417}
418
419fn has_right_neighbor(sector: &HashSet<Position>, p: Position) -> bool {
420 sector.contains(&(p + (0, 1)))
421}
422
423fn has_left_top_neighbor(sector: &HashSet<Position>, p: Position) -> bool {
424 p.row > 0 && p.col > 0 && sector.contains(&(p - (1, 1)))
425}
426
427fn has_right_top_neighbor(sector: &HashSet<Position>, p: Position) -> bool {
428 p.row > 0 && sector.contains(&(p - (1, 0) + (0, 1)))
429}
430
431fn has_left_bottom_neighbor(sector: &HashSet<Position>, p: Position) -> bool {
432 p.col > 0 && sector.contains(&(p + (1, 0) - (0, 1)))
433}
434
435fn has_right_bottom_neighbor(sector: &HashSet<Position>, p: Position) -> bool {
436 sector.contains(&(p + (1, 1)))
437}
438
439#[cfg(test)]
440mod tests {
441 use super::*;
442
443 #[test]
444 fn test_is_connected() {
445 assert!(is_cell_connected(Position::new(0, 0), Position::new(0, 1)));
446 assert!(is_cell_connected(Position::new(0, 0), Position::new(1, 0)));
447 assert!(!is_cell_connected(Position::new(0, 0), Position::new(1, 1)));
448
449 assert!(is_cell_connected(Position::new(0, 1), Position::new(0, 0)));
450 assert!(is_cell_connected(Position::new(1, 0), Position::new(0, 0)));
451 assert!(!is_cell_connected(Position::new(1, 1), Position::new(0, 0)));
452
453 assert!(is_cell_connected(Position::new(1, 1), Position::new(0, 1)));
454 assert!(is_cell_connected(Position::new(1, 1), Position::new(1, 0)));
455 assert!(is_cell_connected(Position::new(1, 1), Position::new(2, 1)));
456 assert!(is_cell_connected(Position::new(1, 1), Position::new(1, 2)));
457 assert!(!is_cell_connected(Position::new(1, 1), Position::new(1, 1)));
458 }
459}