Skip to main content

annotate_snippets/renderer/
margin.rs

1use core::cmp::{max, min};
2
3const ELLIPSIS_PASSING: usize = 6;
4const LONG_WHITESPACE: usize = 20;
5const LONG_WHITESPACE_PADDING: usize = 4;
6
7#[derive(Clone, Copy, Debug, PartialEq)]
8pub(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    pub(crate) 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
26impl 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        // `whitespace_left` should really be thought of as any whitespace
36        // before of the left-most span. This makes it so we don't try and trim
37        // an annotation that is annotating whitespace.
38        // ```
39        //   |
40        // 1 |             x
41        //   |    ^
42        //```
43        let whitespace_left = min(whitespace_left, span_left);
44
45        // The 6 is padding to give a bit of room for `...` when displaying:
46        // ```
47        // error: message
48        //   --> file.rs:16:58
49        //    |
50        // 16 | ... fn foo(self) -> Self::Bar {
51        //    |                     ^^^^^^^^^
52        // ```
53
54        let mut m = Self {
55            whitespace_left: whitespace_left.saturating_sub(ELLIPSIS_PASSING),
56            span_left: span_left.saturating_sub(ELLIPSIS_PASSING),
57            span_right: span_right + ELLIPSIS_PASSING,
58            computed_left: 0,
59            computed_right: 0,
60            term_width,
61            label_right: label_right + ELLIPSIS_PASSING,
62        };
63        m.compute(max_line_len);
64        m
65    }
66
67    pub(crate) fn was_cut_left(&self) -> bool {
68        self.computed_left > 0
69    }
70
71    fn compute(&mut self, max_line_len: usize) {
72        // When there's a lot of whitespace (>20), we want to trim it as it is useless.
73        self.computed_left = if self.whitespace_left > LONG_WHITESPACE {
74            self.whitespace_left - (LONG_WHITESPACE - LONG_WHITESPACE_PADDING) // We want some padding.
75        } else {
76            0
77        };
78        // We want to show as much as possible, max_line_len is the right-most boundary for the
79        // relevant code.
80        self.computed_right = max(max_line_len, self.computed_left);
81
82        if self.computed_right - self.computed_left > self.term_width {
83            // Trimming only whitespace isn't enough, let's get craftier.
84            if self.label_right.saturating_sub(self.whitespace_left) <= self.term_width
85                // Trimming whitespace when the right-most label is somewhrere
86                // within it would result in the label pointing to the wrong
87                // place
88                && self.label_right >= self.whitespace_left
89            {
90                // Attempt to fit the code window only trimming whitespace.
91                self.computed_left = self.whitespace_left;
92                self.computed_right = self.computed_left + self.term_width;
93            } else if self.label_right - self.span_left <= self.term_width {
94                // Attempt to fit the code window considering only the spans and labels.
95                let padding_left = (self.term_width - (self.label_right - self.span_left)) / 2;
96                self.computed_left = self.span_left.saturating_sub(padding_left);
97                self.computed_right = self.computed_left + self.term_width;
98            } else if self.span_right - self.span_left <= self.term_width {
99                // Attempt to fit the code window considering the spans and labels plus padding.
100                let padding_left = (self.term_width - (self.span_right - self.span_left)) / 5 * 2;
101                self.computed_left = self.span_left.saturating_sub(padding_left);
102                self.computed_right = self.computed_left + self.term_width;
103            } else {
104                // Mostly give up but still don't show the full line.
105                self.computed_left = self.span_left;
106                self.computed_right = self.span_right;
107            }
108        }
109    }
110
111    pub(crate) fn left(&self, line_len: usize) -> usize {
112        min(self.computed_left, line_len)
113    }
114
115    pub(crate) fn right(&self, line_len: usize) -> usize {
116        if line_len.saturating_sub(self.computed_left) <= self.term_width {
117            line_len
118        } else {
119            min(line_len, self.computed_right)
120        }
121    }
122}