1 use std::cmp::{max, min}; 2 use std::fmt; 3 4 use crate::formatter::{get_term_style, style::Stylesheet}; 5 6 /// List of lines to be displayed. 7 pub struct DisplayList<'a> { 8 pub body: Vec<DisplayLine<'a>>, 9 pub stylesheet: Box<dyn Stylesheet>, 10 pub anonymized_line_numbers: bool, 11 pub margin: Option<Margin>, 12 } 13 14 impl<'a> From<Vec<DisplayLine<'a>>> for DisplayList<'a> { from(body: Vec<DisplayLine<'a>>) -> DisplayList<'a>15 fn from(body: Vec<DisplayLine<'a>>) -> DisplayList<'a> { 16 Self { 17 body, 18 anonymized_line_numbers: false, 19 stylesheet: get_term_style(false), 20 margin: None, 21 } 22 } 23 } 24 25 impl<'a> PartialEq for DisplayList<'a> { eq(&self, other: &Self) -> bool26 fn eq(&self, other: &Self) -> bool { 27 self.body == other.body && self.anonymized_line_numbers == other.anonymized_line_numbers 28 } 29 } 30 31 impl<'a> fmt::Debug for DisplayList<'a> { fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result32 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 33 f.debug_struct("DisplayList") 34 .field("body", &self.body) 35 .field("anonymized_line_numbers", &self.anonymized_line_numbers) 36 .finish() 37 } 38 } 39 40 #[derive(Debug, Default, Copy, Clone)] 41 pub struct FormatOptions { 42 pub color: bool, 43 pub anonymized_line_numbers: bool, 44 pub margin: Option<Margin>, 45 } 46 47 #[derive(Clone, Copy, Debug)] 48 pub struct Margin { 49 /// The available whitespace in the left that can be consumed when centering. 50 whitespace_left: usize, 51 /// The column of the beginning of left-most span. 52 span_left: usize, 53 /// The column of the end of right-most span. 54 span_right: usize, 55 /// The beginning of the line to be displayed. 56 computed_left: usize, 57 /// The end of the line to be displayed. 58 computed_right: usize, 59 /// The current width of the terminal. 140 by default and in tests. 60 column_width: usize, 61 /// The end column of a span label, including the span. Doesn't account for labels not in the 62 /// same line as the span. 63 label_right: usize, 64 } 65 66 impl Margin { new( whitespace_left: usize, span_left: usize, span_right: usize, label_right: usize, column_width: usize, max_line_len: usize, ) -> Self67 pub fn new( 68 whitespace_left: usize, 69 span_left: usize, 70 span_right: usize, 71 label_right: usize, 72 column_width: usize, 73 max_line_len: usize, 74 ) -> Self { 75 // The 6 is padding to give a bit of room for `...` when displaying: 76 // ``` 77 // error: message 78 // --> file.rs:16:58 79 // | 80 // 16 | ... fn foo(self) -> Self::Bar { 81 // | ^^^^^^^^^ 82 // ``` 83 84 let mut m = Margin { 85 whitespace_left: whitespace_left.saturating_sub(6), 86 span_left: span_left.saturating_sub(6), 87 span_right: span_right + 6, 88 computed_left: 0, 89 computed_right: 0, 90 column_width, 91 label_right: label_right + 6, 92 }; 93 m.compute(max_line_len); 94 m 95 } 96 was_cut_left(&self) -> bool97 pub(crate) fn was_cut_left(&self) -> bool { 98 self.computed_left > 0 99 } 100 was_cut_right(&self, line_len: usize) -> bool101 pub(crate) fn was_cut_right(&self, line_len: usize) -> bool { 102 let right = 103 if self.computed_right == self.span_right || self.computed_right == self.label_right { 104 // Account for the "..." padding given above. Otherwise we end up with code lines that 105 // do fit but end in "..." as if they were trimmed. 106 self.computed_right - 6 107 } else { 108 self.computed_right 109 }; 110 right < line_len && self.computed_left + self.column_width < line_len 111 } 112 compute(&mut self, max_line_len: usize)113 fn compute(&mut self, max_line_len: usize) { 114 // When there's a lot of whitespace (>20), we want to trim it as it is useless. 115 self.computed_left = if self.whitespace_left > 20 { 116 self.whitespace_left - 16 // We want some padding. 117 } else { 118 0 119 }; 120 // We want to show as much as possible, max_line_len is the right-most boundary for the 121 // relevant code. 122 self.computed_right = max(max_line_len, self.computed_left); 123 124 if self.computed_right - self.computed_left > self.column_width { 125 // Trimming only whitespace isn't enough, let's get craftier. 126 if self.label_right - self.whitespace_left <= self.column_width { 127 // Attempt to fit the code window only trimming whitespace. 128 self.computed_left = self.whitespace_left; 129 self.computed_right = self.computed_left + self.column_width; 130 } else if self.label_right - self.span_left <= self.column_width { 131 // Attempt to fit the code window considering only the spans and labels. 132 let padding_left = (self.column_width - (self.label_right - self.span_left)) / 2; 133 self.computed_left = self.span_left.saturating_sub(padding_left); 134 self.computed_right = self.computed_left + self.column_width; 135 } else if self.span_right - self.span_left <= self.column_width { 136 // Attempt to fit the code window considering the spans and labels plus padding. 137 let padding_left = (self.column_width - (self.span_right - self.span_left)) / 5 * 2; 138 self.computed_left = self.span_left.saturating_sub(padding_left); 139 self.computed_right = self.computed_left + self.column_width; 140 } else { 141 // Mostly give up but still don't show the full line. 142 self.computed_left = self.span_left; 143 self.computed_right = self.span_right; 144 } 145 } 146 } 147 left(&self, line_len: usize) -> usize148 pub(crate) fn left(&self, line_len: usize) -> usize { 149 min(self.computed_left, line_len) 150 } 151 right(&self, line_len: usize) -> usize152 pub(crate) fn right(&self, line_len: usize) -> usize { 153 if line_len.saturating_sub(self.computed_left) <= self.column_width { 154 line_len 155 } else { 156 min(line_len, self.computed_right) 157 } 158 } 159 } 160 161 /// Inline annotation which can be used in either Raw or Source line. 162 #[derive(Debug, PartialEq)] 163 pub struct Annotation<'a> { 164 pub annotation_type: DisplayAnnotationType, 165 pub id: Option<&'a str>, 166 pub label: Vec<DisplayTextFragment<'a>>, 167 } 168 169 /// A single line used in `DisplayList`. 170 #[derive(Debug, PartialEq)] 171 pub enum DisplayLine<'a> { 172 /// A line with `lineno` portion of the slice. 173 Source { 174 lineno: Option<usize>, 175 inline_marks: Vec<DisplayMark>, 176 line: DisplaySourceLine<'a>, 177 }, 178 179 /// A line indicating a folded part of the slice. 180 Fold { inline_marks: Vec<DisplayMark> }, 181 182 /// A line which is displayed outside of slices. 183 Raw(DisplayRawLine<'a>), 184 } 185 186 /// A source line. 187 #[derive(Debug, PartialEq)] 188 pub enum DisplaySourceLine<'a> { 189 /// A line with the content of the Slice. 190 Content { 191 text: &'a str, 192 range: (usize, usize), // meta information for annotation placement. 193 }, 194 195 /// An annotation line which is displayed in context of the slice. 196 Annotation { 197 annotation: Annotation<'a>, 198 range: (usize, usize), 199 annotation_type: DisplayAnnotationType, 200 annotation_part: DisplayAnnotationPart, 201 }, 202 203 /// An empty source line. 204 Empty, 205 } 206 207 /// Raw line - a line which does not have the `lineno` part and is not considered 208 /// a part of the snippet. 209 #[derive(Debug, PartialEq)] 210 pub enum DisplayRawLine<'a> { 211 /// A line which provides information about the location of the given 212 /// slice in the project structure. 213 Origin { 214 path: &'a str, 215 pos: Option<(usize, usize)>, 216 header_type: DisplayHeaderType, 217 }, 218 219 /// An annotation line which is not part of any snippet. 220 Annotation { 221 annotation: Annotation<'a>, 222 223 /// If set to `true`, the annotation will be aligned to the 224 /// lineno delimiter of the snippet. 225 source_aligned: bool, 226 /// If set to `true`, only the label of the `Annotation` will be 227 /// displayed. It allows for a multiline annotation to be aligned 228 /// without displaing the meta information (`type` and `id`) to be 229 /// displayed on each line. 230 continuation: bool, 231 }, 232 } 233 234 /// An inline text fragment which any label is composed of. 235 #[derive(Debug, PartialEq)] 236 pub struct DisplayTextFragment<'a> { 237 pub content: &'a str, 238 pub style: DisplayTextStyle, 239 } 240 241 /// A style for the `DisplayTextFragment` which can be visually formatted. 242 /// 243 /// This information may be used to emphasis parts of the label. 244 #[derive(Debug, Clone, Copy, PartialEq)] 245 pub enum DisplayTextStyle { 246 Regular, 247 Emphasis, 248 } 249 250 /// An indicator of what part of the annotation a given `Annotation` is. 251 #[derive(Debug, Clone, PartialEq)] 252 pub enum DisplayAnnotationPart { 253 /// A standalone, single-line annotation. 254 Standalone, 255 /// A continuation of a multi-line label of an annotation. 256 LabelContinuation, 257 /// A consequitive annotation in case multiple annotations annotate a single line. 258 Consequitive, 259 /// A line starting a multiline annotation. 260 MultilineStart, 261 /// A line ending a multiline annotation. 262 MultilineEnd, 263 } 264 265 /// A visual mark used in `inline_marks` field of the `DisplaySourceLine`. 266 #[derive(Debug, Clone, PartialEq)] 267 pub struct DisplayMark { 268 pub mark_type: DisplayMarkType, 269 pub annotation_type: DisplayAnnotationType, 270 } 271 272 /// A type of the `DisplayMark`. 273 #[derive(Debug, Clone, PartialEq)] 274 pub enum DisplayMarkType { 275 /// A mark indicating a multiline annotation going through the current line. 276 AnnotationThrough, 277 /// A mark indicating a multiline annotation starting on the given line. 278 AnnotationStart, 279 } 280 281 /// A type of the `Annotation` which may impact the sigils, style or text displayed. 282 /// 283 /// There are several ways to uses this information when formatting the `DisplayList`: 284 /// 285 /// * An annotation may display the name of the type like `error` or `info`. 286 /// * An underline for `Error` may be `^^^` while for `Warning` it coule be `---`. 287 /// * `ColorStylesheet` may use different colors for different annotations. 288 #[derive(Debug, Clone, PartialEq)] 289 pub enum DisplayAnnotationType { 290 None, 291 Error, 292 Warning, 293 Info, 294 Note, 295 Help, 296 } 297 298 /// Information whether the header is the initial one or a consequitive one 299 /// for multi-slice cases. 300 // TODO: private 301 #[derive(Debug, Clone, PartialEq)] 302 pub enum DisplayHeaderType { 303 /// Initial header is the first header in the snippet. 304 Initial, 305 306 /// Continuation marks all headers of following slices in the snippet. 307 Continuation, 308 } 309