Implement better SourceCodeDisplay output
This commit is contained in:
parent
e0d913612b
commit
8b6fc24759
252
src/base/log.rs
252
src/base/log.rs
|
@ -1,9 +1,9 @@
|
||||||
//! Module containing structures and implementations for logging messages to the user.
|
//! Module containing structures and implementations for logging messages to the user.
|
||||||
|
|
||||||
use colored::Colorize;
|
use colored::Colorize;
|
||||||
use std::fmt::Display;
|
use std::{fmt::Display, sync::Arc};
|
||||||
|
|
||||||
use super::source_file::Span;
|
use super::source_file::{Location, SourceFile, Span};
|
||||||
|
|
||||||
/// Represent the severity of a log message to be printed to the console.
|
/// Represent the severity of a log message to be printed to the console.
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||||
|
@ -45,6 +45,17 @@ impl<T: Display> Display for Message<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_digit_count(mut number: usize) -> usize {
|
||||||
|
let mut digit = 0;
|
||||||
|
|
||||||
|
while number > 0 {
|
||||||
|
number /= 10;
|
||||||
|
digit += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
digit
|
||||||
|
}
|
||||||
|
|
||||||
/// Structure implementing [`Display`] that prints the particular span of the source code.
|
/// Structure implementing [`Display`] that prints the particular span of the source code.
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
pub struct SourceCodeDisplay<'a, T> {
|
pub struct SourceCodeDisplay<'a, T> {
|
||||||
|
@ -64,12 +75,243 @@ impl<'a, T> SourceCodeDisplay<'a, T> {
|
||||||
|
|
||||||
impl<'a, T: std::fmt::Display> Display for SourceCodeDisplay<'a, T> {
|
impl<'a, T: std::fmt::Display> Display for SourceCodeDisplay<'a, T> {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
write!(f, "{}", self.span.str())?;
|
let start_location = self.span.start_location();
|
||||||
|
let end_location = self.span.end_location();
|
||||||
|
|
||||||
if let Some(help_display) = &self.help_display {
|
let start_line = start_location.line;
|
||||||
write!(f, "\n\n{help_display}")?;
|
let end_line = end_location.map_or_else(
|
||||||
|
|| self.span.source_file().line_amount(),
|
||||||
|
|end_location| end_location.line,
|
||||||
|
);
|
||||||
|
let is_multiline = start_line != end_line;
|
||||||
|
|
||||||
|
// when printing the source code, show the line before the span and the line after the span
|
||||||
|
let largest_line_number_digits = get_digit_count(end_line + 1);
|
||||||
|
|
||||||
|
// prints the source location
|
||||||
|
for _ in 0..largest_line_number_digits {
|
||||||
|
write!(f, " ")?;
|
||||||
|
}
|
||||||
|
|
||||||
|
writeln!(
|
||||||
|
f,
|
||||||
|
"{} {}",
|
||||||
|
"-->".cyan().bold(),
|
||||||
|
format_args!(
|
||||||
|
"{}:{}:{}",
|
||||||
|
self.span.source_file().path().display(),
|
||||||
|
start_location.line,
|
||||||
|
start_location.column
|
||||||
|
)
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// prints the empty pipe
|
||||||
|
write_empty_pipe(f, largest_line_number_digits)?;
|
||||||
|
|
||||||
|
// prints previous line
|
||||||
|
if let Some((line_number, line)) = start_line.checked_sub(1).and_then(|line_number| {
|
||||||
|
self.span
|
||||||
|
.source_file()
|
||||||
|
.get_line(line_number)
|
||||||
|
.map(|line| (line_number, line))
|
||||||
|
}) {
|
||||||
|
write_line(f, largest_line_number_digits, line_number, line)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// prints the line with the error
|
||||||
|
write_error_line(
|
||||||
|
f,
|
||||||
|
start_location,
|
||||||
|
end_location,
|
||||||
|
largest_line_number_digits,
|
||||||
|
is_multiline,
|
||||||
|
self.span.source_file(),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
if let Some(message) = &self.help_display {
|
||||||
|
if !is_multiline {
|
||||||
|
// prints the empty pipe
|
||||||
|
{
|
||||||
|
for _ in 0..=largest_line_number_digits {
|
||||||
|
write!(f, " ")?;
|
||||||
|
}
|
||||||
|
write!(f, "{} ", "┃".cyan().bold())?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// prints the whitespace until the start's column
|
||||||
|
{
|
||||||
|
for (index, ch) in self
|
||||||
|
.span
|
||||||
|
.source_file()
|
||||||
|
.get_line(start_line)
|
||||||
|
.unwrap()
|
||||||
|
.chars()
|
||||||
|
.enumerate()
|
||||||
|
{
|
||||||
|
if index + 1 >= start_location.column {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the char is tab, print 4 spaces
|
||||||
|
write!(f, "{}", if ch == '\t' { " " } else { " " })?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// prints the message
|
||||||
|
writeln!(f, "{}: {message}", "help".bold())?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// prints the post line
|
||||||
|
if let Some((line_number, line)) = end_line.checked_add(1).and_then(|line_number| {
|
||||||
|
self.span
|
||||||
|
.source_file()
|
||||||
|
.get_line(line_number)
|
||||||
|
.map(|line| (line_number, line))
|
||||||
|
}) {
|
||||||
|
write_line(f, largest_line_number_digits, line_number, line)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// prints the empty pipe
|
||||||
|
write_empty_pipe(f, largest_line_number_digits)?;
|
||||||
|
|
||||||
|
if is_multiline {
|
||||||
|
if let Some(help_display) = &self.help_display {
|
||||||
|
write_help_message_multiline(f, largest_line_number_digits, help_display)?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn write_empty_pipe(
|
||||||
|
f: &mut std::fmt::Formatter<'_>,
|
||||||
|
largest_line_number_digits: usize,
|
||||||
|
) -> std::fmt::Result {
|
||||||
|
for _ in 0..=largest_line_number_digits {
|
||||||
|
write!(f, " ")?;
|
||||||
|
}
|
||||||
|
writeln!(f, "{}", "┃".cyan().bold())?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_line(
|
||||||
|
f: &mut std::fmt::Formatter<'_>,
|
||||||
|
largest_line_number_digits: usize,
|
||||||
|
line_number: usize,
|
||||||
|
line: &str,
|
||||||
|
) -> std::fmt::Result {
|
||||||
|
// prints the line number
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"{}{}{} ",
|
||||||
|
line_number.to_string().cyan().bold(),
|
||||||
|
format_args!(
|
||||||
|
"{:width$}",
|
||||||
|
"",
|
||||||
|
width = largest_line_number_digits - get_digit_count(line_number) + 1
|
||||||
|
),
|
||||||
|
"┃".cyan().bold(),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
for ch in line.chars() {
|
||||||
|
// if the char is tab, print 4 spaces
|
||||||
|
if ch == '\t' {
|
||||||
|
write!(f, " ")?;
|
||||||
|
} else if ch != '\n' {
|
||||||
|
write!(f, "{ch}")?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
writeln!(f)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_help_message_multiline<T: Display>(
|
||||||
|
f: &mut std::fmt::Formatter<'_>,
|
||||||
|
largest_line_number_digits: usize,
|
||||||
|
help_display: T,
|
||||||
|
) -> std::fmt::Result {
|
||||||
|
for _ in 0..=largest_line_number_digits {
|
||||||
|
write!(f, " ")?;
|
||||||
|
}
|
||||||
|
write!(f, "{} ", "=".cyan().bold())?;
|
||||||
|
|
||||||
|
// prints the message
|
||||||
|
writeln!(f, "{}: {help_display}", "help".bold())?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_error_line(
|
||||||
|
f: &mut std::fmt::Formatter<'_>,
|
||||||
|
start_location: Location,
|
||||||
|
end_location: Option<Location>,
|
||||||
|
largest_line_number_digits: usize,
|
||||||
|
is_multiline: bool,
|
||||||
|
source_file: &Arc<SourceFile>,
|
||||||
|
) -> std::fmt::Result {
|
||||||
|
let start_line = start_location.line;
|
||||||
|
let end_line = end_location.map_or_else(
|
||||||
|
|| source_file.line_amount(),
|
||||||
|
|end_location| end_location.line,
|
||||||
|
);
|
||||||
|
|
||||||
|
for line_number in start_line..=end_line {
|
||||||
|
// prints the line number
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"{}{}{} ",
|
||||||
|
line_number.to_string().cyan().bold(),
|
||||||
|
format_args!(
|
||||||
|
"{:width$}",
|
||||||
|
"",
|
||||||
|
width = largest_line_number_digits - get_digit_count(line_number) + 1
|
||||||
|
),
|
||||||
|
"┃".cyan().bold(),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
for (index, ch) in source_file
|
||||||
|
.get_line(line_number)
|
||||||
|
.unwrap()
|
||||||
|
.chars()
|
||||||
|
.enumerate()
|
||||||
|
{
|
||||||
|
// if the char is tab, print 4 spaces
|
||||||
|
if ch == '\t' {
|
||||||
|
write!(f, " ")?;
|
||||||
|
} else if ch != '\n' {
|
||||||
|
// check if the character is in the span
|
||||||
|
let is_in_span = {
|
||||||
|
let index = index + 1;
|
||||||
|
if is_multiline {
|
||||||
|
(line_number == start_line && index >= start_location.column)
|
||||||
|
|| (line_number == end_line
|
||||||
|
&& (index + 1)
|
||||||
|
< end_location
|
||||||
|
.map_or(usize::MAX, |end_location| end_location.column))
|
||||||
|
|| (line_number > start_line && line_number < end_line)
|
||||||
|
} else {
|
||||||
|
line_number == start_line
|
||||||
|
&& index >= start_location.column
|
||||||
|
&& index
|
||||||
|
< end_location
|
||||||
|
.map_or(usize::MAX, |end_location| end_location.column)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if is_in_span {
|
||||||
|
write!(f, "{}", ch.to_string().red().bold().underline())?;
|
||||||
|
} else {
|
||||||
|
write!(f, "{ch}")?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
writeln!(f)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
|
@ -17,9 +17,13 @@ use super::Error;
|
||||||
|
|
||||||
/// Represents a source file that contains the source code.
|
/// Represents a source file that contains the source code.
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
#[derive(Clone)]
|
#[derive(Clone, Getters)]
|
||||||
pub struct SourceFile {
|
pub struct SourceFile {
|
||||||
|
/// Get the path of the source file.
|
||||||
|
#[get = "pub"]
|
||||||
path: PathBuf,
|
path: PathBuf,
|
||||||
|
/// Get the content of the source file
|
||||||
|
#[get = "pub"]
|
||||||
content: String,
|
content: String,
|
||||||
lines: Vec<Range<usize>>,
|
lines: Vec<Range<usize>>,
|
||||||
}
|
}
|
||||||
|
@ -45,18 +49,6 @@ impl SourceFile {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the content of the source file
|
|
||||||
#[must_use]
|
|
||||||
pub fn content(&self) -> &str {
|
|
||||||
&self.content
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the path of the source file.
|
|
||||||
#[must_use]
|
|
||||||
pub fn path(&self) -> &Path {
|
|
||||||
&self.path
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the line of the source file at the given line number.
|
/// Get the line of the source file at the given line number.
|
||||||
///
|
///
|
||||||
/// Numbering starts at 1.
|
/// Numbering starts at 1.
|
||||||
|
|
Loading…
Reference in New Issue