1 | use std::cmp::{max, min}; |
2 | |
3 | const ELLIPSIS_PASSING: usize = 6; |
4 | const LONG_WHITESPACE: usize = 20; |
5 | const LONG_WHITESPACE_PADDING: usize = 4; |
6 | |
7 | #[derive (Clone, Copy, Debug)] |
8 | pub struct Margin { |
9 | /// The available whitespace in the left that can be consumed when centering. |
10 | whitespace_left: usize, |
11 | /// The column of the beginning of left-most span. |
12 | span_left: usize, |
13 | /// The column of the end of right-most span. |
14 | span_right: usize, |
15 | /// The beginning of the line to be displayed. |
16 | computed_left: usize, |
17 | /// The end of the line to be displayed. |
18 | computed_right: usize, |
19 | /// The current width of the terminal. 140 by default and in tests. |
20 | column_width: usize, |
21 | /// The end column of a span label, including the span. Doesn't account for labels not in the |
22 | /// same line as the span. |
23 | label_right: usize, |
24 | } |
25 | |
26 | impl Margin { |
27 | pub fn new( |
28 | whitespace_left: usize, |
29 | span_left: usize, |
30 | span_right: usize, |
31 | label_right: usize, |
32 | column_width: usize, |
33 | max_line_len: usize, |
34 | ) -> Self { |
35 | // The 6 is padding to give a bit of room for `...` when displaying: |
36 | // ``` |
37 | // error: message |
38 | // --> file.rs:16:58 |
39 | // | |
40 | // 16 | ... fn foo(self) -> Self::Bar { |
41 | // | ^^^^^^^^^ |
42 | // ``` |
43 | |
44 | let mut m = Margin { |
45 | whitespace_left: whitespace_left.saturating_sub(ELLIPSIS_PASSING), |
46 | span_left: span_left.saturating_sub(ELLIPSIS_PASSING), |
47 | span_right: span_right + ELLIPSIS_PASSING, |
48 | computed_left: 0, |
49 | computed_right: 0, |
50 | column_width, |
51 | label_right: label_right + ELLIPSIS_PASSING, |
52 | }; |
53 | m.compute(max_line_len); |
54 | m |
55 | } |
56 | |
57 | pub(crate) fn was_cut_left(&self) -> bool { |
58 | self.computed_left > 0 |
59 | } |
60 | |
61 | pub(crate) fn was_cut_right(&self, line_len: usize) -> bool { |
62 | let right = |
63 | if self.computed_right == self.span_right || self.computed_right == self.label_right { |
64 | // Account for the "..." padding given above. Otherwise we end up with code lines that |
65 | // do fit but end in "..." as if they were trimmed. |
66 | self.computed_right - ELLIPSIS_PASSING |
67 | } else { |
68 | self.computed_right |
69 | }; |
70 | right < line_len && self.computed_left + self.column_width < line_len |
71 | } |
72 | |
73 | fn compute(&mut self, max_line_len: usize) { |
74 | // When there's a lot of whitespace (>20), we want to trim it as it is useless. |
75 | self.computed_left = if self.whitespace_left > LONG_WHITESPACE { |
76 | self.whitespace_left - (LONG_WHITESPACE - LONG_WHITESPACE_PADDING) // We want some padding. |
77 | } else { |
78 | 0 |
79 | }; |
80 | // We want to show as much as possible, max_line_len is the right-most boundary for the |
81 | // relevant code. |
82 | self.computed_right = max(max_line_len, self.computed_left); |
83 | |
84 | if self.computed_right - self.computed_left > self.column_width { |
85 | // Trimming only whitespace isn't enough, let's get craftier. |
86 | if self.label_right - self.whitespace_left <= self.column_width { |
87 | // Attempt to fit the code window only trimming whitespace. |
88 | self.computed_left = self.whitespace_left; |
89 | self.computed_right = self.computed_left + self.column_width; |
90 | } else if self.label_right - self.span_left <= self.column_width { |
91 | // Attempt to fit the code window considering only the spans and labels. |
92 | let padding_left = (self.column_width - (self.label_right - self.span_left)) / 2; |
93 | self.computed_left = self.span_left.saturating_sub(padding_left); |
94 | self.computed_right = self.computed_left + self.column_width; |
95 | } else if self.span_right - self.span_left <= self.column_width { |
96 | // Attempt to fit the code window considering the spans and labels plus padding. |
97 | let padding_left = (self.column_width - (self.span_right - self.span_left)) / 5 * 2; |
98 | self.computed_left = self.span_left.saturating_sub(padding_left); |
99 | self.computed_right = self.computed_left + self.column_width; |
100 | } else { |
101 | // Mostly give up but still don't show the full line. |
102 | self.computed_left = self.span_left; |
103 | self.computed_right = self.span_right; |
104 | } |
105 | } |
106 | } |
107 | |
108 | pub(crate) fn left(&self, line_len: usize) -> usize { |
109 | min(self.computed_left, line_len) |
110 | } |
111 | |
112 | pub(crate) fn right(&self, line_len: usize) -> usize { |
113 | if line_len.saturating_sub(self.computed_left) <= self.column_width { |
114 | line_len |
115 | } else { |
116 | min(line_len, self.computed_right) |
117 | } |
118 | } |
119 | } |
120 | |