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 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308
use std::cmp::{max, min};
use std::fmt;
use crate::formatter::{get_term_style, style::Stylesheet};
/// List of lines to be displayed.
pub struct DisplayList<'a> {
pub body: Vec<DisplayLine<'a>>,
pub stylesheet: Box<dyn Stylesheet>,
pub anonymized_line_numbers: bool,
pub margin: Option<Margin>,
}
impl<'a> From<Vec<DisplayLine<'a>>> for DisplayList<'a> {
fn from(body: Vec<DisplayLine<'a>>) -> DisplayList<'a> {
Self {
body,
anonymized_line_numbers: false,
stylesheet: get_term_style(false),
margin: None,
}
}
}
impl<'a> PartialEq for DisplayList<'a> {
fn eq(&self, other: &Self) -> bool {
self.body == other.body && self.anonymized_line_numbers == other.anonymized_line_numbers
}
}
impl<'a> fmt::Debug for DisplayList<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("DisplayList")
.field("body", &self.body)
.field("anonymized_line_numbers", &self.anonymized_line_numbers)
.finish()
}
}
#[derive(Debug, Default, Copy, Clone)]
pub struct FormatOptions {
pub color: bool,
pub anonymized_line_numbers: bool,
pub margin: Option<Margin>,
}
#[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(6),
span_left: span_left.saturating_sub(6),
span_right: span_right + 6,
computed_left: 0,
computed_right: 0,
column_width,
label_right: label_right + 6,
};
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 - 6
} 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 > 20 {
self.whitespace_left - 16 // 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)
}
}
}
/// Inline annotation which can be used in either Raw or Source line.
#[derive(Debug, PartialEq)]
pub struct Annotation<'a> {
pub annotation_type: DisplayAnnotationType,
pub id: Option<&'a str>,
pub label: Vec<DisplayTextFragment<'a>>,
}
/// A single line used in `DisplayList`.
#[derive(Debug, PartialEq)]
pub enum DisplayLine<'a> {
/// A line with `lineno` portion of the slice.
Source {
lineno: Option<usize>,
inline_marks: Vec<DisplayMark>,
line: DisplaySourceLine<'a>,
},
/// A line indicating a folded part of the slice.
Fold { inline_marks: Vec<DisplayMark> },
/// A line which is displayed outside of slices.
Raw(DisplayRawLine<'a>),
}
/// A source line.
#[derive(Debug, PartialEq)]
pub enum DisplaySourceLine<'a> {
/// A line with the content of the Slice.
Content {
text: &'a str,
range: (usize, usize), // meta information for annotation placement.
},
/// An annotation line which is displayed in context of the slice.
Annotation {
annotation: Annotation<'a>,
range: (usize, usize),
annotation_type: DisplayAnnotationType,
annotation_part: DisplayAnnotationPart,
},
/// An empty source line.
Empty,
}
/// Raw line - a line which does not have the `lineno` part and is not considered
/// a part of the snippet.
#[derive(Debug, PartialEq)]
pub enum DisplayRawLine<'a> {
/// A line which provides information about the location of the given
/// slice in the project structure.
Origin {
path: &'a str,
pos: Option<(usize, usize)>,
header_type: DisplayHeaderType,
},
/// An annotation line which is not part of any snippet.
Annotation {
annotation: Annotation<'a>,
/// If set to `true`, the annotation will be aligned to the
/// lineno delimiter of the snippet.
source_aligned: bool,
/// If set to `true`, only the label of the `Annotation` will be
/// displayed. It allows for a multiline annotation to be aligned
/// without displaing the meta information (`type` and `id`) to be
/// displayed on each line.
continuation: bool,
},
}
/// An inline text fragment which any label is composed of.
#[derive(Debug, PartialEq)]
pub struct DisplayTextFragment<'a> {
pub content: &'a str,
pub style: DisplayTextStyle,
}
/// A style for the `DisplayTextFragment` which can be visually formatted.
///
/// This information may be used to emphasis parts of the label.
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum DisplayTextStyle {
Regular,
Emphasis,
}
/// An indicator of what part of the annotation a given `Annotation` is.
#[derive(Debug, Clone, PartialEq)]
pub enum DisplayAnnotationPart {
/// A standalone, single-line annotation.
Standalone,
/// A continuation of a multi-line label of an annotation.
LabelContinuation,
/// A consequitive annotation in case multiple annotations annotate a single line.
Consequitive,
/// A line starting a multiline annotation.
MultilineStart,
/// A line ending a multiline annotation.
MultilineEnd,
}
/// A visual mark used in `inline_marks` field of the `DisplaySourceLine`.
#[derive(Debug, Clone, PartialEq)]
pub struct DisplayMark {
pub mark_type: DisplayMarkType,
pub annotation_type: DisplayAnnotationType,
}
/// A type of the `DisplayMark`.
#[derive(Debug, Clone, PartialEq)]
pub enum DisplayMarkType {
/// A mark indicating a multiline annotation going through the current line.
AnnotationThrough,
/// A mark indicating a multiline annotation starting on the given line.
AnnotationStart,
}
/// A type of the `Annotation` which may impact the sigils, style or text displayed.
///
/// There are several ways to uses this information when formatting the `DisplayList`:
///
/// * An annotation may display the name of the type like `error` or `info`.
/// * An underline for `Error` may be `^^^` while for `Warning` it coule be `---`.
/// * `ColorStylesheet` may use different colors for different annotations.
#[derive(Debug, Clone, PartialEq)]
pub enum DisplayAnnotationType {
None,
Error,
Warning,
Info,
Note,
Help,
}
/// Information whether the header is the initial one or a consequitive one
/// for multi-slice cases.
// TODO: private
#[derive(Debug, Clone, PartialEq)]
pub enum DisplayHeaderType {
/// Initial header is the first header in the snippet.
Initial,
/// Continuation marks all headers of following slices in the snippet.
Continuation,
}