1 /* Classes for printing labelled rulers.
2 Copyright (C) 2023-2024 Free Software Foundation, Inc.
3 Contributed by David Malcolm <dmalcolm@redhat.com>.
5 This file is part of GCC.
7 GCC is free software; you can redistribute it and/or modify it under
8 the terms of the GNU General Public License as published by the Free
9 Software Foundation; either version 3, or (at your option) any later
12 GCC is distributed in the hope that it will be useful, but WITHOUT ANY
13 WARRANTY; without even the implied warranty of MERCHANTABILITY or
14 FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
17 You should have received a copy of the GNU General Public License
18 along with GCC; see the file COPYING3. If not see
19 <http://www.gnu.org/licenses/>. */
22 #define INCLUDE_ALGORITHM
23 #define INCLUDE_VECTOR
25 #include "coretypes.h"
26 #include "pretty-print.h"
28 #include "text-art/selftests.h"
29 #include "text-art/ruler.h"
30 #include "text-art/theme.h"
32 using namespace text_art
;
35 x_ruler::add_label (const canvas::range_t
&r
,
40 m_labels
.push_back (label (r
, std::move (text
), style_id
, kind
));
45 x_ruler::get_canvas_y (int rel_y
) const
47 gcc_assert (rel_y
>= 0);
48 gcc_assert (rel_y
< m_size
.h
);
53 case label_dir::ABOVE
:
54 return m_size
.h
- (rel_y
+ 1);
55 case label_dir::BELOW
:
61 x_ruler::paint_to_canvas (canvas
&canvas
,
62 canvas::coord_t offset
,
68 canvas
.fill (canvas::rect_t (offset
, m_size
),
69 canvas::cell_t ('*'));
71 for (size_t idx
= 0; idx
< m_labels
.size (); idx
++)
73 const label
&iter_label
= m_labels
[idx
];
75 /* Paint the ruler itself. */
76 const int ruler_rel_y
= get_canvas_y (0);
77 for (int rel_x
= iter_label
.m_range
.start
;
78 rel_x
< iter_label
.m_range
.next
;
81 enum theme::cell_kind kind
= theme::cell_kind::X_RULER_MIDDLE
;
83 if (rel_x
== iter_label
.m_range
.start
)
85 kind
= theme::cell_kind::X_RULER_LEFT_EDGE
;
88 const label
&prev_label
= m_labels
[idx
- 1];
89 if (prev_label
.m_range
.get_max () == iter_label
.m_range
.start
)
90 kind
= theme::cell_kind::X_RULER_INTERNAL_EDGE
;
93 else if (rel_x
== iter_label
.m_range
.get_max ())
94 kind
= theme::cell_kind::X_RULER_RIGHT_EDGE
;
95 else if (rel_x
== iter_label
.m_connector_x
)
101 case label_dir::ABOVE
:
102 kind
= theme::cell_kind::X_RULER_CONNECTOR_TO_LABEL_ABOVE
;
104 case label_dir::BELOW
:
105 kind
= theme::cell_kind::X_RULER_CONNECTOR_TO_LABEL_BELOW
;
109 canvas
.paint (canvas::coord_t (rel_x
, ruler_rel_y
) + offset
,
110 theme
.get_cell (kind
, iter_label
.m_style_id
));
113 /* Paint the connector to the text. */
114 for (int connector_rel_y
= 1;
115 connector_rel_y
< iter_label
.m_text_rect
.get_min_y ();
119 ((canvas::coord_t (iter_label
.m_connector_x
,
120 get_canvas_y (connector_rel_y
))
122 theme
.get_cell (theme::cell_kind::X_RULER_VERTICAL_CONNECTOR
,
123 iter_label
.m_style_id
));
126 /* Paint the text. */
127 switch (iter_label
.m_kind
)
131 case x_ruler::label_kind::TEXT
:
133 ((canvas::coord_t (iter_label
.m_text_rect
.get_min_x (),
134 get_canvas_y (iter_label
.m_text_rect
.get_min_y ()))
139 case x_ruler::label_kind::TEXT_WITH_BORDER
:
141 const canvas::range_t rel_x_range
142 (iter_label
.m_text_rect
.get_x_range ());
144 enum theme::cell_kind inner_left_kind
;
145 enum theme::cell_kind inner_connector_kind
;
146 enum theme::cell_kind inner_right_kind
;
147 enum theme::cell_kind outer_left_kind
;
148 enum theme::cell_kind outer_right_kind
;
154 case label_dir::ABOVE
:
155 outer_left_kind
= theme::cell_kind::TEXT_BORDER_TOP_LEFT
;
156 outer_right_kind
= theme::cell_kind::TEXT_BORDER_TOP_RIGHT
;
157 inner_left_kind
= theme::cell_kind::TEXT_BORDER_BOTTOM_LEFT
;
158 inner_connector_kind
= theme::cell_kind::X_RULER_CONNECTOR_TO_LABEL_BELOW
;
159 inner_right_kind
= theme::cell_kind::TEXT_BORDER_BOTTOM_RIGHT
;
161 case label_dir::BELOW
:
162 inner_left_kind
= theme::cell_kind::TEXT_BORDER_TOP_LEFT
;
163 inner_connector_kind
= theme::cell_kind::X_RULER_CONNECTOR_TO_LABEL_ABOVE
;
164 inner_right_kind
= theme::cell_kind::TEXT_BORDER_TOP_RIGHT
;
165 outer_left_kind
= theme::cell_kind::TEXT_BORDER_BOTTOM_LEFT
;
166 outer_right_kind
= theme::cell_kind::TEXT_BORDER_BOTTOM_RIGHT
;
171 const int rel_canvas_y
172 = get_canvas_y (iter_label
.m_text_rect
.get_min_y ());
174 canvas
.paint ((canvas::coord_t (rel_x_range
.get_min (),
177 theme
.get_cell (inner_left_kind
,
178 iter_label
.m_style_id
));
180 const canvas::cell_t edge_border_cell
181 = theme
.get_cell (theme::cell_kind::TEXT_BORDER_HORIZONTAL
,
182 iter_label
.m_style_id
);
183 const canvas::cell_t connector_border_cell
184 = theme
.get_cell (inner_connector_kind
,
185 iter_label
.m_style_id
);
186 for (int rel_x
= rel_x_range
.get_min () + 1;
187 rel_x
< rel_x_range
.get_max ();
189 if (rel_x
== iter_label
.m_connector_x
)
190 canvas
.paint ((canvas::coord_t (rel_x
, rel_canvas_y
)
192 connector_border_cell
);
194 canvas
.paint ((canvas::coord_t (rel_x
, rel_canvas_y
)
199 canvas
.paint ((canvas::coord_t (rel_x_range
.get_max (),
202 theme
.get_cell (inner_right_kind
,
203 iter_label
.m_style_id
));
207 const int rel_canvas_y
208 = get_canvas_y (iter_label
.m_text_rect
.get_min_y () + 1);
209 const canvas::cell_t border_cell
210 = theme
.get_cell (theme::cell_kind::TEXT_BORDER_VERTICAL
,
211 iter_label
.m_style_id
);
214 canvas
.paint ((canvas::coord_t (rel_x_range
.get_min (),
219 canvas
.paint_text ((canvas::coord_t (rel_x_range
.get_min () + 1,
224 canvas
.paint ((canvas::coord_t (rel_x_range
.get_max (),
232 const int rel_canvas_y
233 = get_canvas_y (iter_label
.m_text_rect
.get_max_y ());
235 canvas
.paint ((canvas::coord_t (rel_x_range
.get_min (),
238 theme
.get_cell (outer_left_kind
,
239 iter_label
.m_style_id
));
241 const canvas::cell_t border_cell
242 = theme
.get_cell (theme::cell_kind::TEXT_BORDER_HORIZONTAL
,
243 iter_label
.m_style_id
);
244 for (int rel_x
= rel_x_range
.get_min () + 1;
245 rel_x
< rel_x_range
.get_max ();
247 canvas
.paint ((canvas::coord_t (rel_x
, rel_canvas_y
)
252 canvas
.paint ((canvas::coord_t (rel_x_range
.get_max (),
255 theme
.get_cell (outer_right_kind
,
256 iter_label
.m_style_id
));
265 x_ruler::debug (const style_manager
&sm
)
267 canvas
c (get_size (), sm
);
268 paint_to_canvas (c
, canvas::coord_t (0, 0), unicode_theme ());
272 x_ruler::label::label (const canvas::range_t
&range
,
274 style::id_t style_id
,
277 m_text (std::move (text
)),
278 m_style_id (style_id
),
280 m_text_rect (canvas::coord_t (0, 0),
281 canvas::size_t (m_text
.calc_canvas_width (), 1)),
282 m_connector_x ((m_range
.get_min () + m_range
.get_max ()) / 2)
284 if (kind
== label_kind::TEXT_WITH_BORDER
)
286 m_text_rect
.m_size
.w
+= 2;
287 m_text_rect
.m_size
.h
+= 2;
292 x_ruler::label::operator< (const label
&other
) const
294 int cmp
= m_range
.start
- other
.m_range
.start
;
297 return m_range
.next
< other
.m_range
.next
;
301 x_ruler::ensure_layout ()
310 x_ruler::update_layout ()
312 if (m_labels
.empty ())
315 std::sort (m_labels
.begin (), m_labels
.end ());
318 int ruler_width
= m_labels
.back ().m_range
.get_next ();
319 int width_with_labels
= ruler_width
;
321 /* Get x coordinates of text parts of each label
322 (m_text_rect.m_top_left.x for each label). */
323 for (size_t idx
= 0; idx
< m_labels
.size (); idx
++)
325 label
&iter_label
= m_labels
[idx
];
326 /* Attempt to center the text label. */
330 /* ...but don't overlap with the connector to the left. */
331 int left_neighbor_connector_x
= m_labels
[idx
- 1].m_connector_x
;
332 min_x
= left_neighbor_connector_x
+ 1;
336 /* ...or go beyond the leftmost column. */
339 int connector_x
= iter_label
.m_connector_x
;
341 = connector_x
- ((int)iter_label
.m_text_rect
.get_width () / 2);
342 int text_x
= std::max (min_x
, centered_x
);
343 iter_label
.m_text_rect
.m_top_left
.x
= text_x
;
346 /* Now walk backwards trying to place them vertically,
347 setting m_text_rect.m_top_left.y for each label,
348 consolidating the rows where possible.
349 The y cooordinates are stored with respect to label_dir::BELOW. */
351 for (int idx
= m_labels
.size () - 1; idx
>= 0; idx
--)
353 label
&iter_label
= m_labels
[idx
];
354 /* Does it fit on the same row as the text label to the right? */
355 size_t text_len
= iter_label
.m_text_rect
.get_width ();
356 /* Get the x-coord of immediately beyond iter_label's text. */
357 int next_x
= iter_label
.m_text_rect
.get_min_x () + text_len
;
358 if (idx
< (int)m_labels
.size () - 1)
360 if (next_x
>= m_labels
[idx
+ 1].m_text_rect
.get_min_x ())
362 /* If not, start a new row. */
363 label_y
+= m_labels
[idx
+ 1].m_text_rect
.get_height ();
366 iter_label
.m_text_rect
.m_top_left
.y
= label_y
;
367 width_with_labels
= std::max (width_with_labels
, next_x
);
370 m_size
= canvas::size_t (width_with_labels
,
371 label_y
+ m_labels
[0].m_text_rect
.get_height ());
379 assert_x_ruler_streq (const location
&loc
,
382 const style_manager
&sm
,
384 const char *expected_str
)
386 canvas
c (ruler
.get_size (), sm
);
387 ruler
.paint_to_canvas (c
, canvas::coord_t (0, 0), theme
);
390 assert_canvas_streq (loc
, c
, styled
, expected_str
);
393 #define ASSERT_X_RULER_STREQ(RULER, THEME, SM, STYLED, EXPECTED_STR) \
394 SELFTEST_BEGIN_STMT \
395 assert_x_ruler_streq ((SELFTEST_LOCATION), \
407 x_ruler
r (x_ruler::label_dir::BELOW
);
408 r
.add_label (canvas::range_t (0, 11), styled_string (sm
, "foo"),
409 style::id_plain
, x_ruler::label_kind::TEXT
);
411 (r
, ascii_theme (), sm
, true,
416 (r
, unicode_theme (), sm
, true,
426 x_ruler
r (x_ruler::label_dir::ABOVE
);
427 r
.add_label (canvas::range_t (0, 11), styled_string (sm
, "hello world"),
430 (r
, ascii_theme (), sm
, true,
435 (r
, unicode_theme (), sm
, true,
442 test_multiple_contiguous ()
445 x_ruler
r (x_ruler::label_dir::BELOW
);
446 r
.add_label (canvas::range_t (0, 11), styled_string (sm
, "foo"),
448 r
.add_label (canvas::range_t (10, 16), styled_string (sm
, "bar"),
451 (r
, ascii_theme (), sm
, true,
452 ("|~~~~+~~~~|~+~~|\n"
456 (r
, unicode_theme (), sm
, true,
457 ("├────┬────┼─┬──┤\n"
463 test_multiple_contiguous_above ()
466 x_ruler
r (x_ruler::label_dir::ABOVE
);
467 r
.add_label (canvas::range_t (0, 11), styled_string (sm
, "foo"),
469 r
.add_label (canvas::range_t (10, 16), styled_string (sm
, "bar"),
472 (r
, ascii_theme (), sm
, true,
475 "|~~~~+~~~~|~+~~|\n"));
477 (r
, unicode_theme (), sm
, true,
480 "├────┴────┼─┴──┤\n"));
484 test_multiple_contiguous_abutting_labels ()
487 x_ruler
r (x_ruler::label_dir::BELOW
);
488 r
.add_label (canvas::range_t (0, 11), styled_string (sm
, "12345678"),
490 r
.add_label (canvas::range_t (10, 16), styled_string (sm
, "1234678"),
493 (r
, unicode_theme (), sm
, true,
494 ("├────┬────┼─┬──┤\n"
501 test_multiple_contiguous_overlapping_labels ()
504 x_ruler
r (x_ruler::label_dir::BELOW
);
505 r
.add_label (canvas::range_t (0, 11), styled_string (sm
, "123456789"),
507 r
.add_label (canvas::range_t (10, 16), styled_string (sm
, "12346789"),
510 (r
, unicode_theme (), sm
, true,
511 ("├────┬────┼─┬──┤\n"
517 test_abutting_left_border ()
520 x_ruler
r (x_ruler::label_dir::BELOW
);
521 r
.add_label (canvas::range_t (0, 6),
522 styled_string (sm
, "this is a long label"),
525 (r
, unicode_theme (), sm
, true,
528 "this is a long label\n"));
532 test_too_long_to_consolidate_vertically ()
535 x_ruler
r (x_ruler::label_dir::BELOW
);
536 r
.add_label (canvas::range_t (0, 11),
537 styled_string (sm
, "long string A"),
539 r
.add_label (canvas::range_t (10, 16),
540 styled_string (sm
, "long string B"),
543 (r
, unicode_theme (), sm
, true,
544 ("├────┬────┼─┬──┤\n"
551 test_abutting_neighbor ()
554 x_ruler
r (x_ruler::label_dir::BELOW
);
555 r
.add_label (canvas::range_t (0, 11),
556 styled_string (sm
, "very long string A"),
558 r
.add_label (canvas::range_t (10, 16),
559 styled_string (sm
, "very long string B"),
562 (r
, unicode_theme (), sm
, true,
563 ("├────┬────┼─┬──┤\n"
565 " │very long string B\n"
566 "very long string A\n"));
573 x_ruler
r (x_ruler::label_dir::BELOW
);
574 r
.add_label (canvas::range_t (0, 5),
575 styled_string (sm
, "foo"),
577 r
.add_label (canvas::range_t (10, 15),
578 styled_string (sm
, "bar"),
581 (r
, ascii_theme (), sm
, true,
593 s1
.m_fg_color
= style::named_color::YELLOW
;
595 s2
.m_fg_color
= style::named_color::BLUE
;
596 style::id_t sid1
= sm
.get_or_create_id (s1
);
597 style::id_t sid2
= sm
.get_or_create_id (s2
);
599 x_ruler
r (x_ruler::label_dir::BELOW
);
600 r
.add_label (canvas::range_t (0, 5), styled_string (sm
, "foo"), sid1
);
601 r
.add_label (canvas::range_t (10, 15), styled_string (sm
, "bar"), sid2
);
603 (r
, ascii_theme (), sm
, true,
604 ("\e[00;01;33m\e[K|~+~|\e[00m\e[K \e[00;01;34m\e[K|~+~|\e[00m\e[K\n"
605 " \e[00;01;33m\e[K|\e[00m\e[K \e[00;01;34m\e[K|\e[00m\e[K\n"
614 x_ruler
r (x_ruler::label_dir::BELOW
);
615 r
.add_label (canvas::range_t (0, 5),
616 styled_string (sm
, "label 1"),
618 x_ruler::label_kind::TEXT_WITH_BORDER
);
619 r
.add_label (canvas::range_t (10, 15),
620 styled_string (sm
, "label 2"),
622 r
.add_label (canvas::range_t (20, 25),
623 styled_string (sm
, "label 3"),
625 x_ruler::label_kind::TEXT_WITH_BORDER
);
627 (r
, ascii_theme (), sm
, true,
628 "|~+~| |~+~| |~+~|\n"
630 " | label 2 +---+---+\n"
631 "+-+-----+ |label 3|\n"
632 "|label 1| +-------+\n"
635 (r
, unicode_theme (), sm
, true,
636 "├─┬─┤ ├─┬─┤ ├─┬─┤\n"
638 " │ label 2 ╭───┴───╮\n"
639 "╭─┴─────╮ │label 3│\n"
640 "│label 1│ ╰───────╯\n"
644 x_ruler
r (x_ruler::label_dir::ABOVE
);
645 r
.add_label (canvas::range_t (0, 5),
646 styled_string (sm
, "label 1"),
648 x_ruler::label_kind::TEXT_WITH_BORDER
);
649 r
.add_label (canvas::range_t (10, 15),
650 styled_string (sm
, "label 2"),
652 r
.add_label (canvas::range_t (20, 25),
653 styled_string (sm
, "label 3"),
655 x_ruler::label_kind::TEXT_WITH_BORDER
);
657 (r
, ascii_theme (), sm
, true,
659 "|label 1| +-------+\n"
660 "+-+-----+ |label 3|\n"
661 " | label 2 +---+---+\n"
663 "|~+~| |~+~| |~+~|\n");
665 (r
, unicode_theme (), sm
, true,
667 "│label 1│ ╭───────╮\n"
668 "╰─┬─────╯ │label 3│\n"
669 " │ label 2 ╰───┬───╯\n"
671 "├─┴─┤ ├─┴─┤ ├─┴─┤\n");
681 s
.append (styled_string (0x26A0, /* U+26A0 WARNING SIGN. */
683 s
.append (styled_string (sm
, " "));
684 s
.append (styled_string (sm
, "this is a warning"));
686 x_ruler
r (x_ruler::label_dir::BELOW
);
687 r
.add_label (canvas::range_t (0, 5),
690 x_ruler::label_kind::TEXT_WITH_BORDER
);
693 (r
, ascii_theme (), sm
, true,
696 "+-+------------------+\n"
697 "|⚠️ this is a warning|\n"
698 "+--------------------+\n");
701 /* Run all selftests in this file. */
704 text_art_ruler_cc_tests ()
707 test_single_above ();
708 test_multiple_contiguous ();
709 test_multiple_contiguous_above ();
710 test_multiple_contiguous_abutting_labels ();
711 test_multiple_contiguous_overlapping_labels ();
712 test_abutting_left_border ();
713 test_too_long_to_consolidate_vertically ();
714 test_abutting_neighbor ();
721 } // namespace selftest
724 #endif /* #if CHECKING_P */