1use crate::formatting::{MonthInfo, WeekLayout};
2use crate::models::{Calendar, ColorMode, DateDetail, PastDateDisplay, WeekStart, WeekendDisplay};
3use anstyle::{AnsiColor, Color, Effects, RgbColor, Style};
4use chrono::Weekday;
5use chrono::{Datelike, NaiveDate};
6
7#[derive(Debug, Clone, Copy)]
8pub struct ColorValue {
9 pub normal: RgbColor,
10 pub dimmed: RgbColor,
11}
12
13impl ColorValue {
14 pub const fn new(normal: RgbColor, dimmed: RgbColor) -> Self {
15 Self { normal, dimmed }
16 }
17
18 pub fn get_normal_style(&self) -> Style {
19 Style::new().bg_color(Some(Color::Rgb(self.normal)))
20 }
21
22 pub fn get_dimmed_style(&self) -> Style {
23 Style::new().bg_color(Some(Color::Rgb(self.dimmed)))
24 }
25}
26
27#[derive(Debug, Clone)]
28pub struct ColorPalette {
29 colors_enabled: bool,
30}
31
32impl Default for ColorPalette {
33 fn default() -> Self {
34 Self {
35 colors_enabled: !Self::is_color_disabled(),
36 }
37 }
38}
39
40impl ColorPalette {
41 pub fn new() -> Self {
42 Self::default()
43 }
44
45 fn is_color_disabled() -> bool {
46 std::env::var("NO_COLOR").is_ok()
47 }
48
49 pub fn are_colors_enabled(&self) -> bool {
50 self.colors_enabled
51 }
52
53 pub fn get_color_value(name: &str) -> Option<ColorValue> {
54 match name {
55 "orange" => Some(ColorValue::new(
56 RgbColor(255, 143, 64),
57 RgbColor(178, 100, 45),
58 )),
59 "yellow" => Some(ColorValue::new(
60 RgbColor(230, 180, 80),
61 RgbColor(161, 126, 56),
62 )),
63 "green" => Some(ColorValue::new(
64 RgbColor(170, 217, 76),
65 RgbColor(119, 152, 53),
66 )),
67 "blue" => Some(ColorValue::new(
68 RgbColor(89, 194, 255),
69 RgbColor(62, 136, 179),
70 )),
71 "purple" => Some(ColorValue::new(
72 RgbColor(210, 166, 255),
73 RgbColor(147, 116, 179),
74 )),
75 "red" => Some(ColorValue::new(
76 RgbColor(240, 113, 120),
77 RgbColor(168, 79, 84),
78 )),
79 "cyan" => Some(ColorValue::new(
80 RgbColor(149, 230, 203),
81 RgbColor(104, 161, 142),
82 )),
83 "gray" => Some(ColorValue::new(RgbColor(95, 99, 110), RgbColor(67, 69, 77))),
84 "light_orange" => Some(ColorValue::new(
85 RgbColor(255, 180, 84),
86 RgbColor(179, 126, 59),
87 )),
88 "light_yellow" => Some(ColorValue::new(
89 RgbColor(249, 175, 79),
90 RgbColor(174, 123, 55),
91 )),
92 "light_green" => Some(ColorValue::new(
93 RgbColor(145, 179, 98),
94 RgbColor(102, 125, 69),
95 )),
96 "light_blue" => Some(ColorValue::new(
97 RgbColor(83, 189, 250),
98 RgbColor(58, 132, 175),
99 )),
100 "light_purple" => Some(ColorValue::new(
101 RgbColor(210, 166, 255),
102 RgbColor(147, 116, 179),
103 )),
104 "light_red" => Some(ColorValue::new(
105 RgbColor(234, 108, 115),
106 RgbColor(164, 76, 81),
107 )),
108 "light_cyan" => Some(ColorValue::new(
109 RgbColor(144, 225, 198),
110 RgbColor(101, 158, 139),
111 )),
112 _ => None,
113 }
114 }
115
116 pub fn get_style(&self, color_name: &str, dimmed: bool) -> Style {
117 if !self.colors_enabled {
118 return Style::new();
119 }
120
121 if let Some(color_value) = Self::get_color_value(color_name) {
122 if dimmed {
123 color_value.get_dimmed_style()
124 } else {
125 color_value.get_normal_style()
126 }
127 } else {
128 Style::new()
129 }
130 }
131
132 pub fn black_text() -> Style {
133 Style::new().fg_color(Some(Color::Ansi(AnsiColor::Black)))
134 }
135}
136
137struct ColorCodes;
138
139impl ColorCodes {
140 fn is_color_disabled() -> bool {
141 std::env::var("NO_COLOR").is_ok()
142 }
143
144 fn get_bg_color(color: &str) -> Style {
145 if Self::is_color_disabled() {
146 return Style::new();
147 }
148 let palette = ColorPalette::new();
149 palette.get_style(color, false)
150 }
151
152 fn get_dimmed_bg_color(color: &str) -> Style {
153 if Self::is_color_disabled() {
154 return Style::new();
155 }
156 let palette = ColorPalette::new();
157 palette.get_style(color, true)
158 }
159
160 fn black_text() -> Style {
161 ColorPalette::black_text()
162 }
163
164 fn underline() -> Effects {
165 Effects::UNDERLINE
166 }
167
168 fn strikethrough() -> Effects {
169 Effects::STRIKETHROUGH
170 }
171
172 fn dim() -> Effects {
173 Effects::DIMMED
174 }
175}
176
177const DAYS_IN_WEEK: usize = 7;
178const CALENDAR_WIDTH: usize = 34;
179const HEADER_WIDTH: usize = 48;
180
181pub struct CalendarRenderer<'a> {
182 calendar: &'a Calendar,
183}
184
185impl<'a> CalendarRenderer<'a> {
186 pub fn new(calendar: &'a Calendar) -> Self {
187 CalendarRenderer { calendar }
188 }
189
190 pub fn render(&self) {
191 self.print_header();
192 self.print_weeks();
193 println!();
194 }
195
196 pub fn render_to_string(&self) -> String {
197 let mut output = String::new();
198
199 let prev_no_color = std::env::var("NO_COLOR").ok();
200 std::env::set_var("NO_COLOR", "1");
201
202 output.push_str(&self.header_to_string());
203 output.push_str(&self.weeks_to_string());
204 output.push('\n');
205
206 match prev_no_color {
207 Some(val) => std::env::set_var("NO_COLOR", val),
208 None => std::env::remove_var("NO_COLOR"),
209 }
210
211 output
212 }
213
214 fn should_render_week(&self, layout: &WeekLayout) -> bool {
216 layout.dates.iter().any(|date| {
218 if date.year() != self.calendar.year {
219 false
220 } else {
221 self.calendar
222 .month_filter
223 .should_display_month(date.month(), self.calendar.year)
224 }
225 })
226 }
227
228 fn get_filtered_date_range(&self) -> (NaiveDate, NaiveDate) {
230 self.calendar
231 .month_filter
232 .get_date_range(self.calendar.year)
233 }
234
235 fn header_to_string(&self) -> String {
236 let mut output = String::new();
237 output.push_str(&format!("┌{:─<width$}┐\n", "", width = HEADER_WIDTH));
238
239 let title = format!("COMPACT CALENDAR {}", self.calendar.year);
241 output.push_str(&format!("│{:^width$}│\n", title, width = HEADER_WIDTH));
242
243 output.push_str(&format!("├{:─<width$}┤\n", "", width = HEADER_WIDTH));
244 output.push_str("│ ");
245 match self.calendar.week_start {
246 WeekStart::Monday => output.push_str("Mon Tue Wed Thu Fri Sat Sun │\n"),
247 WeekStart::Sunday => output.push_str("Sun Mon Tue Wed Thu Fri Sat │\n"),
248 }
249 output
250 }
251
252 fn weeks_to_string(&self) -> String {
253 let mut output = String::new();
254 let (start_date, end_date) = self.get_filtered_date_range();
255
256 let mut current_date = self.align_to_week_start(start_date);
257 let mut week_num = 1;
258 let mut current_month: Option<u32> = None;
259
260 let mut details_queue: Vec<(NaiveDate, DateDetail)> = Vec::new();
261 let mut shown_ranges: Vec<usize> = Vec::new();
262
263 let mut is_first_month = true;
264
265 while current_date <= end_date {
266 let layout = WeekLayout::new(current_date);
267
268 if !self.should_render_week(&layout) {
270 current_date = current_date
271 .checked_add_signed(chrono::Duration::days(DAYS_IN_WEEK as i64))
272 .unwrap();
273 continue;
274 }
275
276 let next_week_date = current_date
277 .checked_add_signed(chrono::Duration::days(DAYS_IN_WEEK as i64))
278 .unwrap();
279 let next_layout = WeekLayout::new(next_week_date);
280
281 if let Some((_, month)) = layout.month_start_idx {
282 current_month = Some(month);
283 if is_first_month {
284 output.push_str(&self.month_border_to_string(&layout, current_month));
285 is_first_month = false;
286 }
287 }
288
289 self.collect_details(&layout, &mut details_queue);
290
291 output.push_str(&self.week_row_to_string(week_num, &layout, current_month));
292
293 output.push_str(&self.annotations_to_string(
294 &layout,
295 &mut details_queue,
296 &mut shown_ranges,
297 ));
298
299 output.push('\n');
300
301 let is_last_week =
302 next_week_date.year() > self.calendar.year || next_week_date > end_date;
303
304 if is_last_week {
305 let mut month_boundary_idx = None;
306 for (idx, &date) in layout.dates.iter().enumerate() {
307 if idx > 0 {
308 let prev_date = layout.dates[idx - 1];
309 if date.month() != prev_date.month() || date.year() != prev_date.year() {
310 month_boundary_idx = Some(idx);
311 break;
312 }
313 }
314 }
315
316 if let Some(boundary_idx) = month_boundary_idx {
317 let dashes_before = (boundary_idx - 1) * 5 + 4;
318 let dashes_after = (DAYS_IN_WEEK - boundary_idx) * 5 - 1;
319 output.push_str(&format!(
320 "└{:─<13}┴{:─<before$}┴{:─<after$}┘\n",
321 "",
322 "",
323 "",
324 before = dashes_before,
325 after = dashes_after
326 ));
327 } else {
328 output.push_str(&format!(
329 "└{:─<13}┴{:─<width$}┘\n",
330 "",
331 "",
332 width = CALENDAR_WIDTH
333 ));
334 }
335 } else if let Some((idx, _)) = layout.month_start_idx {
336 if idx > 0 {
337 output.push_str(&self.separator_to_string(&layout, current_month));
338 }
339 } else if next_layout.month_start_idx.is_some()
340 && next_week_date <= end_date
341 && next_week_date.year() == self.calendar.year
342 {
343 output.push_str(&self.separator_before_month_to_string(
344 &layout,
345 current_month,
346 &next_layout,
347 ));
348 }
349
350 current_date = next_week_date;
351 week_num += 1;
352
353 if current_date.year() > self.calendar.year {
354 break;
355 }
356 }
357
358 output
359 }
360
361 fn month_border_to_string(&self, layout: &WeekLayout, _current_month: Option<u32>) -> String {
362 let mut output = String::new();
363 if let Some((idx, _)) = layout.month_start_idx {
364 if idx > 0 {
365 output.push_str("│ ┌");
366 let dashes_before = (idx - 1) * 5 + 4;
367 for _ in 0..dashes_before {
368 output.push('─');
369 }
370 output.push('┬');
371 let dashes_after = (DAYS_IN_WEEK - idx) * 5 - 1;
372 output.push_str(&format!("{:─<width$}┤\n", "", width = dashes_after));
373 }
374 }
375 output
376 }
377
378 fn week_row_to_string(
379 &self,
380 week_num: i32,
381 layout: &WeekLayout,
382 _current_month: Option<u32>,
383 ) -> String {
384 let mut output = String::new();
385 let month_name = if let Some((_, month)) = layout.month_start_idx {
386 MonthInfo::from_month(month).name
387 } else {
388 ""
389 };
390
391 if !month_name.is_empty() {
392 output.push_str(&format!("│W{:02} {:<9}", week_num, month_name));
393 } else {
394 output.push_str(&format!("│W{:02} ", week_num));
395 }
396
397 output.push('│');
398
399 for (idx, &date) in layout.dates.iter().enumerate() {
400 let is_month_boundary = if idx > 0 {
401 let prev_date = layout.dates[idx - 1];
402 date.month() != prev_date.month() || date.year() != prev_date.year()
403 } else {
404 false
405 };
406
407 if is_month_boundary {
408 output.push('│');
409 }
410
411 output.push_str(&format!(" {:02}", date.day()));
412
413 if idx < 6 {
414 let next_date = layout.dates[idx + 1];
415 let next_is_boundary =
416 date.month() != next_date.month() || date.year() != next_date.year();
417 if next_is_boundary {
418 output.push(' ');
419 } else {
420 output.push_str(" ");
421 }
422 } else {
423 output.push(' ');
424 }
425 }
426
427 output.push('│');
428 output
429 }
430
431 fn annotations_to_string(
432 &self,
433 layout: &WeekLayout,
434 details_queue: &mut Vec<(NaiveDate, DateDetail)>,
435 shown_ranges: &mut Vec<usize>,
436 ) -> String {
437 let mut output = String::new();
438 let week_start = layout.dates[0];
439 let week_end = layout.dates[DAYS_IN_WEEK - 1];
440 let mut annotations = Vec::new();
441
442 let mut details_to_remove = Vec::new();
444 for (i, (detail_date, detail)) in details_queue.iter().enumerate() {
445 if *detail_date >= week_start && *detail_date <= week_end {
446 annotations.push(format!(
447 "{} - {}",
448 detail_date.format("%m/%d"),
449 detail.description
450 ));
451 details_to_remove.push(i);
452 }
453 }
454 for &i in details_to_remove.iter().rev() {
456 details_queue.remove(i);
457 }
458
459 for (idx, range) in self.calendar.ranges.iter().enumerate() {
461 if !shown_ranges.contains(&idx) && range.start <= week_end && range.end >= week_start {
462 if let Some(desc) = &range.description {
463 annotations.push(format!(
464 "{} to {} - {}",
465 range.start.format("%m/%d"),
466 range.end.format("%m/%d"),
467 desc
468 ));
469 } else {
470 annotations.push(format!(
471 "{} to {}",
472 range.start.format("%m/%d"),
473 range.end.format("%m/%d")
474 ));
475 }
476 shown_ranges.push(idx);
477 }
478 }
479
480 output.push_str(&annotations.join(", "));
482
483 output
484 }
485
486 fn separator_to_string(&self, layout: &WeekLayout, current_month: Option<u32>) -> String {
487 let mut output = String::new();
488 output.push_str("│ ├");
489
490 let mut first_bar_idx = None;
491 for (idx, &date) in layout.dates.iter().enumerate() {
492 let in_month = date.year() == self.calendar.year && Some(date.month()) == current_month;
493 let prev_in_month = if idx > 0 {
494 let prev_date = layout.dates[idx - 1];
495 prev_date.year() == self.calendar.year && Some(prev_date.month()) == current_month
496 } else {
497 false
498 };
499
500 if in_month && !prev_in_month {
501 first_bar_idx = Some(idx);
502 }
503 }
504
505 if let Some(bar_idx) = first_bar_idx {
506 if bar_idx > 0 {
507 let dashes = (bar_idx - 1) * 5 + 4;
508 output.push_str(&format!("{:─<width$}┘", "", width = dashes));
509 let spaces = (DAYS_IN_WEEK - bar_idx) * 5 - 1;
510 output.push_str(&format!("{: <width$}│\n", "", width = spaces));
511 } else {
512 output.push_str("───────────────────────────────┤│\n");
513 }
514 } else {
515 output.push_str("───────────────────────────────┤│\n");
516 }
517
518 output
519 }
520
521 fn separator_before_month_to_string(
522 &self,
523 _current_layout: &WeekLayout,
524 _current_month: Option<u32>,
525 next_layout: &WeekLayout,
526 ) -> String {
527 let mut output = String::new();
528 if let Some((next_month_start_idx, _)) = next_layout.month_start_idx {
529 if next_month_start_idx == 0 {
530 output.push_str("│ ├");
531 output.push_str(&format!("{:─<width$}┤", "", width = CALENDAR_WIDTH));
532 } else {
533 output.push_str("│ │");
534 let spaces_before = (next_month_start_idx - 1) * 5 + 4;
535 output.push_str(&format!("{: <width$}┌", "", width = spaces_before));
536 let dashes = (DAYS_IN_WEEK - 1 - next_month_start_idx) * 5 + 4;
537 output.push_str(&format!("{:─<width$}┤", "", width = dashes));
538 }
539 } else {
540 output.push_str("│ │");
541 output.push_str(&format!("{: <width$}", "", width = DAYS_IN_WEEK * 4 + 3));
542 }
543
544 output.push('\n');
545 output
546 }
547
548 fn print_header(&self) {
549 print!("{}", self.header_to_string());
550 }
551
552 fn print_weeks(&self) {
553 let (start_date, end_date) = self.get_filtered_date_range();
554
555 let mut current_date = self.align_to_week_start(start_date);
556 let mut week_num = 1;
557 let mut current_month: Option<u32> = None;
558
559 let mut details_queue: Vec<(NaiveDate, DateDetail)> = Vec::new();
560 let mut shown_ranges: Vec<usize> = Vec::new();
561
562 let mut is_first_month = true;
563
564 while current_date <= end_date {
565 let layout = WeekLayout::new(current_date);
566
567 if !self.should_render_week(&layout) {
569 current_date = current_date
570 .checked_add_signed(chrono::Duration::days(DAYS_IN_WEEK as i64))
571 .unwrap();
572 continue;
573 }
574
575 let next_week_date = current_date
576 .checked_add_signed(chrono::Duration::days(DAYS_IN_WEEK as i64))
577 .unwrap();
578 let next_layout = WeekLayout::new(next_week_date);
579
580 if let Some((_, month)) = layout.month_start_idx {
581 current_month = Some(month);
582 if is_first_month {
583 self.print_month_border(&layout, current_month);
584 is_first_month = false;
585 }
586 }
587
588 self.collect_details(&layout, &mut details_queue);
589
590 self.print_week_row(week_num, &layout, current_month);
591
592 self.print_annotations(&layout, &mut details_queue, &mut shown_ranges);
593
594 println!();
595
596 let is_last_week =
597 next_week_date.year() > self.calendar.year || next_week_date > end_date;
598
599 if is_last_week {
600 let mut month_boundary_idx = None;
601 for (idx, &date) in layout.dates.iter().enumerate() {
602 if idx > 0 {
603 let prev_date = layout.dates[idx - 1];
604 if date.month() != prev_date.month() || date.year() != prev_date.year() {
605 month_boundary_idx = Some(idx);
606 break;
607 }
608 }
609 }
610
611 if let Some(boundary_idx) = month_boundary_idx {
612 let dashes_before = (boundary_idx - 1) * 5 + 4;
613 let dashes_after = (DAYS_IN_WEEK - boundary_idx) * 5 - 1;
614 println!(
615 "└{:─<13}┴{:─<before$}┴{:─<after$}┘",
616 "",
617 "",
618 "",
619 before = dashes_before,
620 after = dashes_after
621 );
622 } else {
623 println!("└{:─<13}┴{:─<width$}┘", "", "", width = CALENDAR_WIDTH);
624 }
625 } else if let Some((idx, _)) = layout.month_start_idx {
626 if idx > 0 {
627 self.print_separator(&layout, current_month);
628 }
629 } else if next_layout.month_start_idx.is_some()
630 && next_week_date <= end_date
631 && next_week_date.year() == self.calendar.year
632 {
633 self.print_separator_before_month(&layout, current_month, &next_layout);
634 }
635
636 current_date = next_week_date;
637 week_num += 1;
638
639 if current_date.year() > self.calendar.year {
640 break;
641 }
642 }
643 }
644
645 fn align_to_week_start(&self, date: NaiveDate) -> NaiveDate {
646 let mut aligned = date;
647 while self.calendar.get_weekday_num(aligned) != 0 {
648 aligned = aligned.pred_opt().unwrap();
649 }
650 aligned
651 }
652
653 fn get_date_color(&self, date: NaiveDate) -> Option<String> {
654 if self.calendar.color_mode == ColorMode::Work
656 && (date.weekday() == Weekday::Sat || date.weekday() == Weekday::Sun)
657 {
658 return None;
659 }
660
661 if let Some(detail) = self.calendar.details.get(&date) {
663 if let Some(color) = &detail.color {
664 return Some(color.clone());
665 }
666 }
667
668 for range in &self.calendar.ranges {
670 if date >= range.start && date <= range.end {
671 return Some(range.color.clone());
672 }
673 }
674
675 None
676 }
677
678 fn print_month_border(&self, layout: &WeekLayout, current_month: Option<u32>) {
679 print!("{}", self.month_border_to_string(layout, current_month));
680 }
681
682 fn collect_details(
683 &self,
684 layout: &WeekLayout,
685 details_queue: &mut Vec<(NaiveDate, DateDetail)>,
686 ) {
687 for &date in &layout.dates {
688 if let Some(detail) = self.calendar.details.get(&date) {
689 if !details_queue.iter().any(|(d, _)| d == &date) {
690 details_queue.push((date, detail.clone()));
691 }
692 }
693 }
694 }
695
696 fn print_week_row(&self, week_num: i32, layout: &WeekLayout, _current_month: Option<u32>) {
697 let month_name = if let Some((_, month)) = layout.month_start_idx {
698 MonthInfo::from_month(month).name
699 } else {
700 ""
701 };
702
703 if !month_name.is_empty() {
704 print!("│W{:02} {:<9}", week_num, month_name);
705 } else {
706 print!("│W{:02} ", week_num);
707 }
708
709 print!("│");
710
711 for (idx, &date) in layout.dates.iter().enumerate() {
712 let is_month_boundary = if idx > 0 {
713 let prev_date = layout.dates[idx - 1];
714 date.month() != prev_date.month() || date.year() != prev_date.year()
715 } else {
716 false
717 };
718
719 if is_month_boundary {
720 print!("│");
721 }
722
723 let today = chrono::Local::now().date_naive();
724 let is_today = date == today;
725 let is_past =
726 self.calendar.past_date_display == PastDateDisplay::Strikethrough && date < today;
727
728 let is_weekend = self.calendar.weekend_display == WeekendDisplay::Dimmed
729 && (date.weekday() == Weekday::Sat || date.weekday() == Weekday::Sun);
730
731 if let Some(color) = self.get_date_color(date) {
732 let mut style = if is_weekend {
733 ColorCodes::get_dimmed_bg_color(&color)
734 } else {
735 ColorCodes::get_bg_color(&color)
736 };
737
738 if ColorCodes::is_color_disabled() {
739 print!(" {:02}", date.day());
740 } else {
741 style = style.fg_color(ColorCodes::black_text().get_fg_color());
742
743 let mut effects = Effects::new();
744 if is_past {
745 effects |= ColorCodes::strikethrough();
746 }
747 if is_today {
748 effects |= ColorCodes::underline();
749 }
750 style = style.effects(effects);
751
752 print!(
753 " {}{:02}{}",
754 style.render(),
755 date.day(),
756 style.render_reset()
757 );
758 }
759 } else if ColorCodes::is_color_disabled() {
760 print!(" {:02}", date.day());
761 } else {
762 let mut style = Style::new();
763 let mut effects = Effects::new();
764
765 if is_past {
766 effects |= ColorCodes::strikethrough();
767 }
768 if is_today {
769 effects |= ColorCodes::underline();
770 }
771 if is_weekend {
772 effects |= ColorCodes::dim();
773 }
774
775 style = style.effects(effects);
776
777 if effects == Effects::new() {
778 print!(" {:02}", date.day());
779 } else {
780 print!(
781 " {}{:02}{}",
782 style.render(),
783 date.day(),
784 style.render_reset()
785 );
786 }
787 }
788
789 if idx < 6 {
790 let next_date = layout.dates[idx + 1];
791 let next_is_boundary =
792 date.month() != next_date.month() || date.year() != next_date.year();
793 if next_is_boundary {
794 print!(" ");
795 } else {
796 print!(" ");
797 }
798 } else {
799 print!(" ");
800 }
801 }
802
803 print!("│");
804 }
805
806 fn print_annotations(
807 &self,
808 layout: &WeekLayout,
809 details_queue: &mut Vec<(NaiveDate, DateDetail)>,
810 shown_ranges: &mut Vec<usize>,
811 ) {
812 let week_start = layout.dates[0];
813 let week_end = layout.dates[DAYS_IN_WEEK - 1];
814 let mut first = true;
815
816 let mut details_to_remove = Vec::new();
818 for (i, (detail_date, detail)) in details_queue.iter().enumerate() {
819 if *detail_date >= week_start && *detail_date <= week_end {
820 if !first {
821 print!(", ");
822 }
823 first = false;
824
825 if ColorCodes::is_color_disabled() {
826 print!("{} - {}", detail_date.format("%m/%d"), detail.description);
827 } else if let Some(color) = &detail.color {
828 let style = ColorCodes::get_bg_color(color)
829 .fg_color(ColorCodes::black_text().get_fg_color());
830 print!(
831 "{}{} - {}{}",
832 style.render(),
833 detail_date.format("%m/%d"),
834 detail.description,
835 style.render_reset()
836 );
837 } else {
838 print!("{} - {}", detail_date.format("%m/%d"), detail.description);
839 }
840 details_to_remove.push(i);
841 }
842 }
843 for &i in details_to_remove.iter().rev() {
845 details_queue.remove(i);
846 }
847
848 for (idx, range) in self.calendar.ranges.iter().enumerate() {
850 if !shown_ranges.contains(&idx) && range.start <= week_end && range.end >= week_start {
851 if !first {
852 print!(", ");
853 }
854 first = false;
855
856 if ColorCodes::is_color_disabled() {
857 if let Some(desc) = &range.description {
858 print!(
859 "{} to {} - {}",
860 range.start.format("%m/%d"),
861 range.end.format("%m/%d"),
862 desc
863 );
864 } else {
865 print!(
866 "{} to {}",
867 range.start.format("%m/%d"),
868 range.end.format("%m/%d")
869 );
870 }
871 } else {
872 let style = ColorCodes::get_bg_color(&range.color)
873 .fg_color(ColorCodes::black_text().get_fg_color());
874
875 if let Some(desc) = &range.description {
876 print!(
877 "{}{} to {} - {}{}",
878 style.render(),
879 range.start.format("%m/%d"),
880 range.end.format("%m/%d"),
881 desc,
882 style.render_reset()
883 );
884 } else {
885 print!(
886 "{}{} to {}{}",
887 style.render(),
888 range.start.format("%m/%d"),
889 range.end.format("%m/%d"),
890 style.render_reset()
891 );
892 }
893 }
894 shown_ranges.push(idx);
895 }
896 }
897 }
898
899 fn print_separator(&self, layout: &WeekLayout, current_month: Option<u32>) {
900 print!("{}", self.separator_to_string(layout, current_month));
901 }
902
903 fn print_separator_before_month(
904 &self,
905 current_layout: &WeekLayout,
906 current_month: Option<u32>,
907 next_layout: &WeekLayout,
908 ) {
909 print!(
910 "{}",
911 self.separator_before_month_to_string(current_layout, current_month, next_layout)
912 );
913 }
914}