1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
use std::cmp::{max, min};

const ELLIPSIS_PASSING: usize = 6;
const LONG_WHITESPACE: usize = 20;
const LONG_WHITESPACE_PADDING: usize = 4;

#[derive(Clone, Copy, Debug)]
pub struct Margin {
    /// The available whitespace in the left that can be consumed when centering.
    whitespace_left: usize,
    /// The column of the beginning of left-most span.
    span_left: usize,
    /// The column of the end of right-most span.
    span_right: usize,
    /// The beginning of the line to be displayed.
    computed_left: usize,
    /// The end of the line to be displayed.
    computed_right: usize,
    /// The current width of the terminal. 140 by default and in tests.
    column_width: usize,
    /// The end column of a span label, including the span. Doesn't account for labels not in the
    /// same line as the span.
    label_right: usize,
}

impl Margin {
    pub fn new(
        whitespace_left: usize,
        span_left: usize,
        span_right: usize,
        label_right: usize,
        column_width: usize,
        max_line_len: usize,
    ) -> Self {
        // The 6 is padding to give a bit of room for `...` when displaying:
        // ```
        // error: message
        //   --> file.rs:16:58
        //    |
        // 16 | ... fn foo(self) -> Self::Bar {
        //    |                     ^^^^^^^^^
        // ```

        let mut m = Margin {
            whitespace_left: whitespace_left.saturating_sub(ELLIPSIS_PASSING),
            span_left: span_left.saturating_sub(ELLIPSIS_PASSING),
            span_right: span_right + ELLIPSIS_PASSING,
            computed_left: 0,
            computed_right: 0,
            column_width,
            label_right: label_right + ELLIPSIS_PASSING,
        };
        m.compute(max_line_len);
        m
    }

    pub(crate) fn was_cut_left(&self) -> bool {
        self.computed_left > 0
    }

    pub(crate) fn was_cut_right(&self, line_len: usize) -> bool {
        let right =
            if self.computed_right == self.span_right || self.computed_right == self.label_right {
                // Account for the "..." padding given above. Otherwise we end up with code lines that
                // do fit but end in "..." as if they were trimmed.
                self.computed_right - ELLIPSIS_PASSING
            } else {
                self.computed_right
            };
        right < line_len && self.computed_left + self.column_width < line_len
    }

    fn compute(&mut self, max_line_len: usize) {
        // When there's a lot of whitespace (>20), we want to trim it as it is useless.
        self.computed_left = if self.whitespace_left > LONG_WHITESPACE {
            self.whitespace_left - (LONG_WHITESPACE - LONG_WHITESPACE_PADDING) // We want some padding.
        } else {
            0
        };
        // We want to show as much as possible, max_line_len is the right-most boundary for the
        // relevant code.
        self.computed_right = max(max_line_len, self.computed_left);

        if self.computed_right - self.computed_left > self.column_width {
            // Trimming only whitespace isn't enough, let's get craftier.
            if self.label_right - self.whitespace_left <= self.column_width {
                // Attempt to fit the code window only trimming whitespace.
                self.computed_left = self.whitespace_left;
                self.computed_right = self.computed_left + self.column_width;
            } else if self.label_right - self.span_left <= self.column_width {
                // Attempt to fit the code window considering only the spans and labels.
                let padding_left = (self.column_width - (self.label_right - self.span_left)) / 2;
                self.computed_left = self.span_left.saturating_sub(padding_left);
                self.computed_right = self.computed_left + self.column_width;
            } else if self.span_right - self.span_left <= self.column_width {
                // Attempt to fit the code window considering the spans and labels plus padding.
                let padding_left = (self.column_width - (self.span_right - self.span_left)) / 5 * 2;
                self.computed_left = self.span_left.saturating_sub(padding_left);
                self.computed_right = self.computed_left + self.column_width;
            } else {
                // Mostly give up but still don't show the full line.
                self.computed_left = self.span_left;
                self.computed_right = self.span_right;
            }
        }
    }

    pub(crate) fn left(&self, line_len: usize) -> usize {
        min(self.computed_left, line_len)
    }

    pub(crate) fn right(&self, line_len: usize) -> usize {
        if line_len.saturating_sub(self.computed_left) <= self.column_width {
            line_len
        } else {
            min(line_len, self.computed_right)
        }
    }
}