| 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, PartialEq)] | 
|---|
| 8 | pub(crate) 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 | term_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(crate) fn new( | 
|---|
| 28 | whitespace_left: usize, | 
|---|
| 29 | span_left: usize, | 
|---|
| 30 | span_right: usize, | 
|---|
| 31 | label_right: usize, | 
|---|
| 32 | term_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 | term_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.term_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.term_width { | 
|---|
| 85 | // Trimming only whitespace isn't enough, let's get craftier. | 
|---|
| 86 | if self.label_right - self.whitespace_left <= self.term_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.term_width; | 
|---|
| 90 | } else if self.label_right - self.span_left <= self.term_width { | 
|---|
| 91 | // Attempt to fit the code window considering only the spans and labels. | 
|---|
| 92 | let padding_left = (self.term_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.term_width; | 
|---|
| 95 | } else if self.span_right - self.span_left <= self.term_width { | 
|---|
| 96 | // Attempt to fit the code window considering the spans and labels plus padding. | 
|---|
| 97 | let padding_left = (self.term_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.term_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.term_width { | 
|---|
| 114 | line_len | 
|---|
| 115 | } else { | 
|---|
| 116 | min(line_len, self.computed_right) | 
|---|
| 117 | } | 
|---|
| 118 | } | 
|---|
| 119 | } | 
|---|
| 120 |  | 
|---|