1 | /* Classes for printing labelled rulers. |
2 | Copyright (C) 2023-2024 Free Software Foundation, Inc. |
3 | Contributed by David Malcolm <dmalcolm@redhat.com>. |
4 | |
5 | This file is part of GCC. |
6 | |
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 |
10 | version. |
11 | |
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 |
15 | for more details. |
16 | |
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/>. */ |
20 | |
21 | #include "config.h" |
22 | #define INCLUDE_ALGORITHM |
23 | #define INCLUDE_VECTOR |
24 | #include "system.h" |
25 | #include "coretypes.h" |
26 | #include "pretty-print.h" |
27 | #include "selftest.h" |
28 | #include "text-art/selftests.h" |
29 | #include "text-art/ruler.h" |
30 | #include "text-art/theme.h" |
31 | |
32 | using namespace text_art; |
33 | |
34 | void |
35 | x_ruler::add_label (const canvas::range_t &r, |
36 | styled_string text, |
37 | style::id_t style_id, |
38 | label_kind kind) |
39 | { |
40 | m_labels.push_back (x: label (r, std::move (text), style_id, kind)); |
41 | m_has_layout = false; |
42 | } |
43 | |
44 | int |
45 | x_ruler::get_canvas_y (int rel_y) const |
46 | { |
47 | gcc_assert (rel_y >= 0); |
48 | gcc_assert (rel_y < m_size.h); |
49 | switch (m_label_dir) |
50 | { |
51 | default: |
52 | gcc_unreachable (); |
53 | case label_dir::ABOVE: |
54 | return m_size.h - (rel_y + 1); |
55 | case label_dir::BELOW: |
56 | return rel_y; |
57 | } |
58 | } |
59 | |
60 | void |
61 | x_ruler::paint_to_canvas (canvas &canvas, |
62 | canvas::coord_t offset, |
63 | const theme &theme) |
64 | { |
65 | ensure_layout (); |
66 | |
67 | if (0) |
68 | canvas.fill (rect: canvas::rect_t (offset, m_size), |
69 | c: canvas::cell_t ('*')); |
70 | |
71 | for (size_t idx = 0; idx < m_labels.size (); idx++) |
72 | { |
73 | const label &iter_label = m_labels[idx]; |
74 | |
75 | /* Paint the ruler itself. */ |
76 | const int ruler_rel_y = get_canvas_y (rel_y: 0); |
77 | for (int rel_x = iter_label.m_range.start; |
78 | rel_x < iter_label.m_range.next; |
79 | rel_x++) |
80 | { |
81 | enum theme::cell_kind kind = theme::cell_kind::X_RULER_MIDDLE; |
82 | |
83 | if (rel_x == iter_label.m_range.start) |
84 | { |
85 | kind = theme::cell_kind::X_RULER_LEFT_EDGE; |
86 | if (idx > 0) |
87 | { |
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; |
91 | } |
92 | } |
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) |
96 | { |
97 | switch (m_label_dir) |
98 | { |
99 | default: |
100 | gcc_unreachable (); |
101 | case label_dir::ABOVE: |
102 | kind = theme::cell_kind::X_RULER_CONNECTOR_TO_LABEL_ABOVE; |
103 | break; |
104 | case label_dir::BELOW: |
105 | kind = theme::cell_kind::X_RULER_CONNECTOR_TO_LABEL_BELOW; |
106 | break; |
107 | } |
108 | } |
109 | canvas.paint (coord: canvas::coord_t (rel_x, ruler_rel_y) + offset, |
110 | c: theme.get_cell (kind, style_idx: iter_label.m_style_id)); |
111 | } |
112 | |
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 (); |
116 | connector_rel_y++) |
117 | { |
118 | canvas.paint |
119 | (coord: (canvas::coord_t (iter_label.m_connector_x, |
120 | get_canvas_y (rel_y: connector_rel_y)) |
121 | + offset), |
122 | c: theme.get_cell (kind: theme::cell_kind::X_RULER_VERTICAL_CONNECTOR, |
123 | style_idx: iter_label.m_style_id)); |
124 | } |
125 | |
126 | /* Paint the text. */ |
127 | switch (iter_label.m_kind) |
128 | { |
129 | default: |
130 | gcc_unreachable (); |
131 | case x_ruler::label_kind::TEXT: |
132 | canvas.paint_text |
133 | (coord: (canvas::coord_t (iter_label.m_text_rect.get_min_x (), |
134 | get_canvas_y (rel_y: iter_label.m_text_rect.get_min_y ())) |
135 | + offset), |
136 | text: iter_label.m_text); |
137 | break; |
138 | |
139 | case x_ruler::label_kind::TEXT_WITH_BORDER: |
140 | { |
141 | const canvas::range_t rel_x_range |
142 | (iter_label.m_text_rect.get_x_range ()); |
143 | |
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; |
149 | |
150 | switch (m_label_dir) |
151 | { |
152 | default: |
153 | gcc_unreachable (); |
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; |
160 | break; |
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; |
167 | break; |
168 | } |
169 | /* Inner border. */ |
170 | { |
171 | const int rel_canvas_y |
172 | = get_canvas_y (rel_y: iter_label.m_text_rect.get_min_y ()); |
173 | /* Left corner. */ |
174 | canvas.paint (coord: (canvas::coord_t (rel_x_range.get_min (), |
175 | rel_canvas_y) |
176 | + offset), |
177 | c: theme.get_cell (kind: inner_left_kind, |
178 | style_idx: iter_label.m_style_id)); |
179 | /* Edge. */ |
180 | const canvas::cell_t edge_border_cell |
181 | = theme.get_cell (kind: theme::cell_kind::TEXT_BORDER_HORIZONTAL, |
182 | style_idx: iter_label.m_style_id); |
183 | const canvas::cell_t connector_border_cell |
184 | = theme.get_cell (kind: inner_connector_kind, |
185 | style_idx: iter_label.m_style_id); |
186 | for (int rel_x = rel_x_range.get_min () + 1; |
187 | rel_x < rel_x_range.get_max (); |
188 | rel_x++) |
189 | if (rel_x == iter_label.m_connector_x) |
190 | canvas.paint (coord: (canvas::coord_t (rel_x, rel_canvas_y) |
191 | + offset), |
192 | c: connector_border_cell); |
193 | else |
194 | canvas.paint (coord: (canvas::coord_t (rel_x, rel_canvas_y) |
195 | + offset), |
196 | c: edge_border_cell); |
197 | |
198 | /* Right corner. */ |
199 | canvas.paint (coord: (canvas::coord_t (rel_x_range.get_max (), |
200 | rel_canvas_y) |
201 | + offset), |
202 | c: theme.get_cell (kind: inner_right_kind, |
203 | style_idx: iter_label.m_style_id)); |
204 | } |
205 | |
206 | { |
207 | const int rel_canvas_y |
208 | = get_canvas_y (rel_y: iter_label.m_text_rect.get_min_y () + 1); |
209 | const canvas::cell_t border_cell |
210 | = theme.get_cell (kind: theme::cell_kind::TEXT_BORDER_VERTICAL, |
211 | style_idx: iter_label.m_style_id); |
212 | |
213 | /* Left border. */ |
214 | canvas.paint (coord: (canvas::coord_t (rel_x_range.get_min (), |
215 | rel_canvas_y) |
216 | + offset), |
217 | c: border_cell); |
218 | /* Text. */ |
219 | canvas.paint_text (coord: (canvas::coord_t (rel_x_range.get_min () + 1, |
220 | rel_canvas_y) |
221 | + offset), |
222 | text: iter_label.m_text); |
223 | /* Right border. */ |
224 | canvas.paint (coord: (canvas::coord_t (rel_x_range.get_max (), |
225 | rel_canvas_y) |
226 | + offset), |
227 | c: border_cell); |
228 | } |
229 | |
230 | /* Outer border. */ |
231 | { |
232 | const int rel_canvas_y |
233 | = get_canvas_y (rel_y: iter_label.m_text_rect.get_max_y ()); |
234 | /* Left corner. */ |
235 | canvas.paint (coord: (canvas::coord_t (rel_x_range.get_min (), |
236 | rel_canvas_y) |
237 | + offset), |
238 | c: theme.get_cell (kind: outer_left_kind, |
239 | style_idx: iter_label.m_style_id)); |
240 | /* Edge. */ |
241 | const canvas::cell_t border_cell |
242 | = theme.get_cell (kind: theme::cell_kind::TEXT_BORDER_HORIZONTAL, |
243 | style_idx: iter_label.m_style_id); |
244 | for (int rel_x = rel_x_range.get_min () + 1; |
245 | rel_x < rel_x_range.get_max (); |
246 | rel_x++) |
247 | canvas.paint (coord: (canvas::coord_t (rel_x, rel_canvas_y) |
248 | + offset), |
249 | c: border_cell); |
250 | |
251 | /* Right corner. */ |
252 | canvas.paint (coord: (canvas::coord_t (rel_x_range.get_max (), |
253 | rel_canvas_y) |
254 | + offset), |
255 | c: theme.get_cell (kind: outer_right_kind, |
256 | style_idx: iter_label.m_style_id)); |
257 | } |
258 | } |
259 | break; |
260 | } |
261 | } |
262 | } |
263 | |
264 | DEBUG_FUNCTION void |
265 | x_ruler::debug (const style_manager &sm) |
266 | { |
267 | canvas c (get_size (), sm); |
268 | paint_to_canvas (canvas&: c, offset: canvas::coord_t (0, 0), theme: unicode_theme ()); |
269 | c.debug (styled: true); |
270 | } |
271 | |
272 | x_ruler::label::label (const canvas::range_t &range, |
273 | styled_string text, |
274 | style::id_t style_id, |
275 | label_kind kind) |
276 | : m_range (range), |
277 | m_text (std::move (text)), |
278 | m_style_id (style_id), |
279 | m_kind (kind), |
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) |
283 | { |
284 | if (kind == label_kind::TEXT_WITH_BORDER) |
285 | { |
286 | m_text_rect.m_size.w += 2; |
287 | m_text_rect.m_size.h += 2; |
288 | } |
289 | } |
290 | |
291 | bool |
292 | x_ruler::label::operator< (const label &other) const |
293 | { |
294 | int cmp = m_range.start - other.m_range.start; |
295 | if (cmp) |
296 | return cmp < 0; |
297 | return m_range.next < other.m_range.next; |
298 | } |
299 | |
300 | void |
301 | x_ruler::ensure_layout () |
302 | { |
303 | if (m_has_layout) |
304 | return; |
305 | update_layout (); |
306 | m_has_layout = true; |
307 | } |
308 | |
309 | void |
310 | x_ruler::update_layout () |
311 | { |
312 | if (m_labels.empty ()) |
313 | return; |
314 | |
315 | std::sort (first: m_labels.begin (), last: m_labels.end ()); |
316 | |
317 | /* Place labels. */ |
318 | int ruler_width = m_labels.back ().m_range.get_next (); |
319 | int width_with_labels = ruler_width; |
320 | |
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++) |
324 | { |
325 | label &iter_label = m_labels[idx]; |
326 | /* Attempt to center the text label. */ |
327 | int min_x; |
328 | if (idx > 0) |
329 | { |
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; |
333 | } |
334 | else |
335 | { |
336 | /* ...or go beyond the leftmost column. */ |
337 | min_x = 0; |
338 | } |
339 | int connector_x = iter_label.m_connector_x; |
340 | int centered_x |
341 | = connector_x - ((int)iter_label.m_text_rect.get_width () / 2); |
342 | int text_x = std::max (a: min_x, b: centered_x); |
343 | iter_label.m_text_rect.m_top_left.x = text_x; |
344 | } |
345 | |
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. */ |
350 | int label_y = 2; |
351 | for (int idx = m_labels.size () - 1; idx >= 0; idx--) |
352 | { |
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) |
359 | { |
360 | if (next_x >= m_labels[idx + 1].m_text_rect.get_min_x ()) |
361 | { |
362 | /* If not, start a new row. */ |
363 | label_y += m_labels[idx + 1].m_text_rect.get_height (); |
364 | } |
365 | } |
366 | iter_label.m_text_rect.m_top_left.y = label_y; |
367 | width_with_labels = std::max (a: width_with_labels, b: next_x); |
368 | } |
369 | |
370 | m_size = canvas::size_t (width_with_labels, |
371 | label_y + m_labels[0].m_text_rect.get_height ()); |
372 | } |
373 | |
374 | #if CHECKING_P |
375 | |
376 | namespace selftest { |
377 | |
378 | static void |
379 | assert_x_ruler_streq (const location &loc, |
380 | x_ruler &ruler, |
381 | const theme &theme, |
382 | const style_manager &sm, |
383 | bool styled, |
384 | const char *expected_str) |
385 | { |
386 | canvas c (ruler.get_size (), sm); |
387 | ruler.paint_to_canvas (canvas&: c, offset: canvas::coord_t (0, 0), theme); |
388 | if (0) |
389 | c.debug (styled); |
390 | assert_canvas_streq (loc, canvas: c, styled, expected_str); |
391 | } |
392 | |
393 | #define ASSERT_X_RULER_STREQ(RULER, THEME, SM, STYLED, EXPECTED_STR) \ |
394 | SELFTEST_BEGIN_STMT \ |
395 | assert_x_ruler_streq ((SELFTEST_LOCATION), \ |
396 | (RULER), \ |
397 | (THEME), \ |
398 | (SM), \ |
399 | (STYLED), \ |
400 | (EXPECTED_STR)); \ |
401 | SELFTEST_END_STMT |
402 | |
403 | static void |
404 | test_single () |
405 | { |
406 | style_manager sm; |
407 | x_ruler r (x_ruler::label_dir::BELOW); |
408 | r.add_label (r: canvas::range_t (0, 11), text: styled_string (sm, "foo" ), |
409 | style_id: style::id_plain, kind: x_ruler::label_kind::TEXT); |
410 | ASSERT_X_RULER_STREQ |
411 | (r, ascii_theme (), sm, true, |
412 | ("|~~~~+~~~~|\n" |
413 | " |\n" |
414 | " foo\n" )); |
415 | ASSERT_X_RULER_STREQ |
416 | (r, unicode_theme (), sm, true, |
417 | ("├────┬────┤\n" |
418 | " │\n" |
419 | " foo\n" )); |
420 | } |
421 | |
422 | static void |
423 | test_single_above () |
424 | { |
425 | style_manager sm; |
426 | x_ruler r (x_ruler::label_dir::ABOVE); |
427 | r.add_label (r: canvas::range_t (0, 11), text: styled_string (sm, "hello world" ), |
428 | style_id: style::id_plain); |
429 | ASSERT_X_RULER_STREQ |
430 | (r, ascii_theme (), sm, true, |
431 | ("hello world\n" |
432 | " |\n" |
433 | "|~~~~+~~~~|\n" )); |
434 | ASSERT_X_RULER_STREQ |
435 | (r, unicode_theme (), sm, true, |
436 | ("hello world\n" |
437 | " │\n" |
438 | "├────┴────┤\n" )); |
439 | } |
440 | |
441 | static void |
442 | test_multiple_contiguous () |
443 | { |
444 | style_manager sm; |
445 | x_ruler r (x_ruler::label_dir::BELOW); |
446 | r.add_label (r: canvas::range_t (0, 11), text: styled_string (sm, "foo" ), |
447 | style_id: style::id_plain); |
448 | r.add_label (r: canvas::range_t (10, 16), text: styled_string (sm, "bar" ), |
449 | style_id: style::id_plain); |
450 | ASSERT_X_RULER_STREQ |
451 | (r, ascii_theme (), sm, true, |
452 | ("|~~~~+~~~~|~+~~|\n" |
453 | " | |\n" |
454 | " foo bar\n" )); |
455 | ASSERT_X_RULER_STREQ |
456 | (r, unicode_theme (), sm, true, |
457 | ("├────┬────┼─┬──┤\n" |
458 | " │ │\n" |
459 | " foo bar\n" )); |
460 | } |
461 | |
462 | static void |
463 | test_multiple_contiguous_above () |
464 | { |
465 | style_manager sm; |
466 | x_ruler r (x_ruler::label_dir::ABOVE); |
467 | r.add_label (r: canvas::range_t (0, 11), text: styled_string (sm, "foo" ), |
468 | style_id: style::id_plain); |
469 | r.add_label (r: canvas::range_t (10, 16), text: styled_string (sm, "bar" ), |
470 | style_id: style::id_plain); |
471 | ASSERT_X_RULER_STREQ |
472 | (r, ascii_theme (), sm, true, |
473 | (" foo bar\n" |
474 | " | |\n" |
475 | "|~~~~+~~~~|~+~~|\n" )); |
476 | ASSERT_X_RULER_STREQ |
477 | (r, unicode_theme (), sm, true, |
478 | (" foo bar\n" |
479 | " │ │\n" |
480 | "├────┴────┼─┴──┤\n" )); |
481 | } |
482 | |
483 | static void |
484 | test_multiple_contiguous_abutting_labels () |
485 | { |
486 | style_manager sm; |
487 | x_ruler r (x_ruler::label_dir::BELOW); |
488 | r.add_label (r: canvas::range_t (0, 11), text: styled_string (sm, "12345678" ), |
489 | style_id: style::id_plain); |
490 | r.add_label (r: canvas::range_t (10, 16), text: styled_string (sm, "1234678" ), |
491 | style_id: style::id_plain); |
492 | ASSERT_X_RULER_STREQ |
493 | (r, unicode_theme (), sm, true, |
494 | ("├────┬────┼─┬──┤\n" |
495 | " │ │\n" |
496 | " │ 1234678\n" |
497 | " 12345678\n" )); |
498 | } |
499 | |
500 | static void |
501 | test_multiple_contiguous_overlapping_labels () |
502 | { |
503 | style_manager sm; |
504 | x_ruler r (x_ruler::label_dir::BELOW); |
505 | r.add_label (r: canvas::range_t (0, 11), text: styled_string (sm, "123456789" ), |
506 | style_id: style::id_plain); |
507 | r.add_label (r: canvas::range_t (10, 16), text: styled_string (sm, "12346789" ), |
508 | style_id: style::id_plain); |
509 | ASSERT_X_RULER_STREQ |
510 | (r, unicode_theme (), sm, true, |
511 | ("├────┬────┼─┬──┤\n" |
512 | " │ │\n" |
513 | " │ 12346789\n" |
514 | " 123456789\n" )); |
515 | } |
516 | static void |
517 | test_abutting_left_border () |
518 | { |
519 | style_manager sm; |
520 | x_ruler r (x_ruler::label_dir::BELOW); |
521 | r.add_label (r: canvas::range_t (0, 6), |
522 | text: styled_string (sm, "this is a long label" ), |
523 | style_id: style::id_plain); |
524 | ASSERT_X_RULER_STREQ |
525 | (r, unicode_theme (), sm, true, |
526 | ("├─┬──┤\n" |
527 | " │\n" |
528 | "this is a long label\n" )); |
529 | } |
530 | |
531 | static void |
532 | test_too_long_to_consolidate_vertically () |
533 | { |
534 | style_manager sm; |
535 | x_ruler r (x_ruler::label_dir::BELOW); |
536 | r.add_label (r: canvas::range_t (0, 11), |
537 | text: styled_string (sm, "long string A" ), |
538 | style_id: style::id_plain); |
539 | r.add_label (r: canvas::range_t (10, 16), |
540 | text: styled_string (sm, "long string B" ), |
541 | style_id: style::id_plain); |
542 | ASSERT_X_RULER_STREQ |
543 | (r, unicode_theme (), sm, true, |
544 | ("├────┬────┼─┬──┤\n" |
545 | " │ │\n" |
546 | " │long string B\n" |
547 | "long string A\n" )); |
548 | } |
549 | |
550 | static void |
551 | test_abutting_neighbor () |
552 | { |
553 | style_manager sm; |
554 | x_ruler r (x_ruler::label_dir::BELOW); |
555 | r.add_label (r: canvas::range_t (0, 11), |
556 | text: styled_string (sm, "very long string A" ), |
557 | style_id: style::id_plain); |
558 | r.add_label (r: canvas::range_t (10, 16), |
559 | text: styled_string (sm, "very long string B" ), |
560 | style_id: style::id_plain); |
561 | ASSERT_X_RULER_STREQ |
562 | (r, unicode_theme (), sm, true, |
563 | ("├────┬────┼─┬──┤\n" |
564 | " │ │\n" |
565 | " │very long string B\n" |
566 | "very long string A\n" )); |
567 | } |
568 | |
569 | static void |
570 | test_gaps () |
571 | { |
572 | style_manager sm; |
573 | x_ruler r (x_ruler::label_dir::BELOW); |
574 | r.add_label (r: canvas::range_t (0, 5), |
575 | text: styled_string (sm, "foo" ), |
576 | style_id: style::id_plain); |
577 | r.add_label (r: canvas::range_t (10, 15), |
578 | text: styled_string (sm, "bar" ), |
579 | style_id: style::id_plain); |
580 | ASSERT_X_RULER_STREQ |
581 | (r, ascii_theme (), sm, true, |
582 | ("|~+~| |~+~|\n" |
583 | " | |\n" |
584 | " foo bar\n" )); |
585 | } |
586 | |
587 | static void |
588 | test_styled () |
589 | { |
590 | style_manager sm; |
591 | style s1, s2; |
592 | s1.m_bold = true; |
593 | s1.m_fg_color = style::named_color::YELLOW; |
594 | s2.m_bold = true; |
595 | s2.m_fg_color = style::named_color::BLUE; |
596 | style::id_t sid1 = sm.get_or_create_id (style: s1); |
597 | style::id_t sid2 = sm.get_or_create_id (style: s2); |
598 | |
599 | x_ruler r (x_ruler::label_dir::BELOW); |
600 | r.add_label (r: canvas::range_t (0, 5), text: styled_string (sm, "foo" ), style_id: sid1); |
601 | r.add_label (r: canvas::range_t (10, 15), text: styled_string (sm, "bar" ), style_id: sid2); |
602 | ASSERT_X_RULER_STREQ |
603 | (r, ascii_theme (), sm, true, |
604 | ("[00;01;33m[K|~+~|[00m[K [00;01;34m[K|~+~|[00m[K\n" |
605 | " [00;01;33m[K|[00m[K [00;01;34m[K|[00m[K\n" |
606 | " foo bar\n" )); |
607 | } |
608 | |
609 | static void |
610 | test_borders () |
611 | { |
612 | style_manager sm; |
613 | { |
614 | x_ruler r (x_ruler::label_dir::BELOW); |
615 | r.add_label (r: canvas::range_t (0, 5), |
616 | text: styled_string (sm, "label 1" ), |
617 | style_id: style::id_plain, |
618 | kind: x_ruler::label_kind::TEXT_WITH_BORDER); |
619 | r.add_label (r: canvas::range_t (10, 15), |
620 | text: styled_string (sm, "label 2" ), |
621 | style_id: style::id_plain); |
622 | r.add_label (r: canvas::range_t (20, 25), |
623 | text: styled_string (sm, "label 3" ), |
624 | style_id: style::id_plain, |
625 | kind: x_ruler::label_kind::TEXT_WITH_BORDER); |
626 | ASSERT_X_RULER_STREQ |
627 | (r, ascii_theme (), sm, true, |
628 | "|~+~| |~+~| |~+~|\n" |
629 | " | | |\n" |
630 | " | label 2 +---+---+\n" |
631 | "+-+-----+ |label 3|\n" |
632 | "|label 1| +-------+\n" |
633 | "+-------+\n" ); |
634 | ASSERT_X_RULER_STREQ |
635 | (r, unicode_theme (), sm, true, |
636 | "├─┬─┤ ├─┬─┤ ├─┬─┤\n" |
637 | " │ │ │\n" |
638 | " │ label 2 ╭───┴───╮\n" |
639 | "╭─┴─────╮ │label 3│\n" |
640 | "│label 1│ ╰───────╯\n" |
641 | "╰───────╯\n" ); |
642 | } |
643 | { |
644 | x_ruler r (x_ruler::label_dir::ABOVE); |
645 | r.add_label (r: canvas::range_t (0, 5), |
646 | text: styled_string (sm, "label 1" ), |
647 | style_id: style::id_plain, |
648 | kind: x_ruler::label_kind::TEXT_WITH_BORDER); |
649 | r.add_label (r: canvas::range_t (10, 15), |
650 | text: styled_string (sm, "label 2" ), |
651 | style_id: style::id_plain); |
652 | r.add_label (r: canvas::range_t (20, 25), |
653 | text: styled_string (sm, "label 3" ), |
654 | style_id: style::id_plain, |
655 | kind: x_ruler::label_kind::TEXT_WITH_BORDER); |
656 | ASSERT_X_RULER_STREQ |
657 | (r, ascii_theme (), sm, true, |
658 | "+-------+\n" |
659 | "|label 1| +-------+\n" |
660 | "+-+-----+ |label 3|\n" |
661 | " | label 2 +---+---+\n" |
662 | " | | |\n" |
663 | "|~+~| |~+~| |~+~|\n" ); |
664 | ASSERT_X_RULER_STREQ |
665 | (r, unicode_theme (), sm, true, |
666 | "╭───────╮\n" |
667 | "│label 1│ ╭───────╮\n" |
668 | "╰─┬─────╯ │label 3│\n" |
669 | " │ label 2 ╰───┬───╯\n" |
670 | " │ │ │\n" |
671 | "├─┴─┤ ├─┴─┤ ├─┴─┤\n" ); |
672 | } |
673 | } |
674 | |
675 | static void |
676 | test_emoji () |
677 | { |
678 | style_manager sm; |
679 | |
680 | styled_string s; |
681 | s.append (suffix: styled_string (0x26A0, /* U+26A0 WARNING SIGN. */ |
682 | true)); |
683 | s.append (suffix: styled_string (sm, " " )); |
684 | s.append (suffix: styled_string (sm, "this is a warning" )); |
685 | |
686 | x_ruler r (x_ruler::label_dir::BELOW); |
687 | r.add_label (r: canvas::range_t (0, 5), |
688 | text: std::move (s), |
689 | style_id: style::id_plain, |
690 | kind: x_ruler::label_kind::TEXT_WITH_BORDER); |
691 | |
692 | ASSERT_X_RULER_STREQ |
693 | (r, ascii_theme (), sm, true, |
694 | "|~+~|\n" |
695 | " |\n" |
696 | "+-+------------------+\n" |
697 | "|⚠️ this is a warning|\n" |
698 | "+--------------------+\n" ); |
699 | } |
700 | |
701 | /* Run all selftests in this file. */ |
702 | |
703 | void |
704 | text_art_ruler_cc_tests () |
705 | { |
706 | test_single (); |
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 (); |
715 | test_gaps (); |
716 | test_styled (); |
717 | test_borders (); |
718 | test_emoji (); |
719 | } |
720 | |
721 | } // namespace selftest |
722 | |
723 | |
724 | #endif /* #if CHECKING_P */ |
725 | |