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
#![allow(dead_code)]
use colored::Colorize;
use pad::PadStr;
use crate::compiling::logging::LogType;
pub struct Displayer {
color: (u8, u8, u8),
row: usize,
col: usize,
len: usize
}
impl Displayer {
/// Create a new Displayer instance
pub fn new(color: (u8, u8, u8), row: usize, col: usize, len: usize) -> Self {
Displayer {
color,
row,
col,
len
}
}
/// Render header of your information
pub fn header(self, kind: LogType) -> Self {
let (r, g, b) = self.color;
let name = match kind {
LogType::Error => " ERROR ".to_string(),
LogType::Warning => " WARN ".to_string(),
LogType::Info => " INFO ".to_string()
};
let formatted = name
.truecolor(0, 0, 0)
.bold()
.on_truecolor(r, g, b);
println!("{formatted}");
self
}
/// Render text with supplied coloring
pub fn text(self, text: Option<String>) -> Self {
let (r, g, b) = self.color;
if let Some(text) = text {
println!("{}", text.truecolor(r, g, b));
}
self
}
/// Render padded text with supplied coloring
pub fn padded_text(self, text: Option<String>) -> Self {
let (r, g, b) = self.color;
if let Some(text) = text {
println!("\n{}", text.truecolor(r, g, b));
}
self
}
/// Render location details with supplied coloring
pub fn path(self, path: Option<String>) -> Self {
let path = path.unwrap_or_else(|| "[unknown]".to_string());
let (r, g, b) = self.color;
let formatted = format!("at {}:{}:{}", path, self.row, self.col)
.truecolor(r, g, b)
.dimmed();
println!("{formatted}");
self
}
// Get max padding size (for line numbering)
fn get_max_pad_size(&self, length: usize) -> usize {
if self.row < length - 1 {
format!("{}", self.row + 1).len()
}
else {
format!("{}", self.row).len()
}
}
// Returns chopped string where fisrt and third part are supposed
// to be left as is but the second one is supposed to be highlighted
fn get_highlighted_part(&self, line: &str) -> [String;3] {
let begin = self.col - 1;
let end = begin + self.len;
let mut results: [String; 3] = Default::default();
for (index, letter) in line.chars().enumerate() {
if index < begin {
results[0].push(letter);
}
else if index >= end {
results[2].push(letter);
}
else {
results[1].push(letter);
}
}
results
}
// Return requested row with appropriate coloring
fn get_snippet_row(&self, code: &Vec<String>, index: usize, offset: i8, overflow: &mut usize) -> String {
let max_pad = self.get_max_pad_size(code.len());
let index = index as i32 + offset as i32;
let row = self.row as i32 + offset as i32;
let code = code[index as usize].clone();
let line = format!("{row}").pad_to_width(max_pad);
let (r, g, b) = self.color;
// Case if we are in the same line as the error (or message)
if offset == 0 {
let slices = self.get_highlighted_part(&code);
let formatted = format!("{}{}{}", slices[0], slices[1].truecolor(r, g, b), slices[2]);
// If we are at the end of the code snippet and there is still some
if self.col - 1 + self.len > code.chars().count() {
// We substract here 2 because 1 is the offset of col (starts at 1)
// and other 1 is the new line character that we do not display
*overflow = self.col - 2 + self.len - code.chars().count();
}
format!("{line}| {formatted}")
}
// Case if we are in a different line than the error (or message)
else {
// If there is some overflow value - display it as well
if *overflow > 0 {
// Case if all line is highlighted
if *overflow > code.chars().count() {
format!("{line}| {}", code.truecolor(r, g, b)).dimmed().to_string()
}
// Case if some line is highlighted
else {
let err = code.get(0..*overflow).unwrap().to_string().truecolor(r, g, b);
let rest = code.get(*overflow..).unwrap().to_string();
format!("{line}| {err}{rest}").dimmed().to_string()
}
}
// Case if no overflow
else {
format!("{line}| {code}").dimmed().to_string()
}
}
}
/// Render snippet of the code if the message is contextual to it
pub fn snippet<T: AsRef<str>>(self, code: Option<T>) {
if let Some(code) = code {
let mut overflow = 0;
let index = self.row - 1;
let code: String = String::from(code.as_ref());
let code = code.split('\n')
.map(|item| item.to_string())
.collect::<Vec<String>>();
println!();
// Show additional code above the snippet
if index > 0 {
println!("{}", self.get_snippet_row(&code, index, -1, &mut overflow));
}
println!("{}", self.get_snippet_row(&code, index, 0, &mut overflow));
// Show additional code below the snippet
if index < code.len() - 1 {
println!("{}", self.get_snippet_row(&code, index, 1, &mut overflow));
}
}
}
}
#[cfg(test)]
mod test {
#![allow(unused_imports)]
use std::time::Duration;
use std::thread::sleep;
#[allow(unused_variables)]
#[test]
fn test_displayer() {
let code = vec![
"let a = 12",
"value = 'this",
"is mutltiline",
"code"
].join("\n");
// Uncomment to see the error message
// sleep(Duration::from_secs(1));
// super::Displayer::new((255, 80, 80), 2, 9, 24)
// .header(super::LogType::Error)
// .text(Some(format!("Cannot call function \"foobar\" on a number")))
// .path(Some(format!("/path/to/file")))
// .snippet(Some(code));
}
}