Remove misc.rs
(#491)
Put everything that was in `misc.rs` into their own files, with some opportunistic refactoring, because why not.
This commit is contained in:
parent
ca4f2a44ed
commit
3de31b3c02
@ -31,9 +31,8 @@ pub(crate) use crate::testing;
|
||||
|
||||
// functions
|
||||
pub(crate) use crate::{
|
||||
load_dotenv::load_dotenv,
|
||||
misc::{default, empty},
|
||||
output::output,
|
||||
default::default, empty::empty, load_dotenv::load_dotenv, output::output,
|
||||
write_message_context::write_message_context,
|
||||
};
|
||||
|
||||
// structs and enums
|
||||
@ -41,12 +40,13 @@ pub(crate) use crate::{
|
||||
alias::Alias, alias_resolver::AliasResolver, assignment_evaluator::AssignmentEvaluator,
|
||||
assignment_resolver::AssignmentResolver, color::Color, compilation_error::CompilationError,
|
||||
compilation_error_kind::CompilationErrorKind, config::Config, config_error::ConfigError,
|
||||
expression::Expression, fragment::Fragment, function::Function,
|
||||
function_context::FunctionContext, functions::Functions, interrupt_guard::InterruptGuard,
|
||||
interrupt_handler::InterruptHandler, justfile::Justfile, lexer::Lexer, output_error::OutputError,
|
||||
parameter::Parameter, parser::Parser, platform::Platform, position::Position, recipe::Recipe,
|
||||
recipe_context::RecipeContext, recipe_resolver::RecipeResolver, runtime_error::RuntimeError,
|
||||
search_error::SearchError, shebang::Shebang, state::State, string_literal::StringLiteral,
|
||||
count::Count, enclosure::Enclosure, expression::Expression, fragment::Fragment,
|
||||
function::Function, function_context::FunctionContext, functions::Functions,
|
||||
interrupt_guard::InterruptGuard, interrupt_handler::InterruptHandler, justfile::Justfile,
|
||||
lexer::Lexer, list::List, output_error::OutputError, parameter::Parameter, parser::Parser,
|
||||
platform::Platform, position::Position, recipe::Recipe, recipe_context::RecipeContext,
|
||||
recipe_resolver::RecipeResolver, runtime_error::RuntimeError, search_error::SearchError,
|
||||
shebang::Shebang, show_whitespace::ShowWhitespace, state::State, string_literal::StringLiteral,
|
||||
subcommand::Subcommand, token::Token, token_kind::TokenKind, use_color::UseColor,
|
||||
variables::Variables, verbosity::Verbosity, warning::Warning,
|
||||
};
|
||||
|
@ -1,7 +1,5 @@
|
||||
use crate::common::*;
|
||||
|
||||
use crate::misc::{maybe_s, show_whitespace, write_message_context, Or};
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub(crate) struct CompilationError<'a> {
|
||||
pub(crate) text: &'a str,
|
||||
@ -82,7 +80,7 @@ impl<'a> Display for CompilationError<'a> {
|
||||
ref expected,
|
||||
found,
|
||||
} => {
|
||||
writeln!(f, "Expected {}, but found {}", Or(expected), found)?;
|
||||
writeln!(f, "Expected {}, but found {}", List::or(expected), found)?;
|
||||
}
|
||||
DuplicateAlias { alias, first } => {
|
||||
writeln!(
|
||||
@ -139,7 +137,7 @@ impl<'a> Display for CompilationError<'a> {
|
||||
f,
|
||||
"Found a mix of tabs and spaces in leading whitespace: `{}`\n\
|
||||
Leading whitespace may consist of tabs or spaces, but not both",
|
||||
show_whitespace(whitespace)
|
||||
ShowWhitespace(whitespace)
|
||||
)?;
|
||||
}
|
||||
ExtraLeadingWhitespace => {
|
||||
@ -152,10 +150,10 @@ impl<'a> Display for CompilationError<'a> {
|
||||
} => {
|
||||
writeln!(
|
||||
f,
|
||||
"Function `{}` called with {} argument{} but takes {}",
|
||||
"Function `{}` called with {} {} but takes {}",
|
||||
function,
|
||||
found,
|
||||
maybe_s(found),
|
||||
Count("argument", found),
|
||||
expected
|
||||
)?;
|
||||
}
|
||||
@ -164,8 +162,8 @@ impl<'a> Display for CompilationError<'a> {
|
||||
f,
|
||||
"Recipe line has inconsistent leading whitespace. \
|
||||
Recipe started with `{}` but found line with `{}`",
|
||||
show_whitespace(expected),
|
||||
show_whitespace(found)
|
||||
ShowWhitespace(expected),
|
||||
ShowWhitespace(found)
|
||||
)?;
|
||||
}
|
||||
UnknownAliasTarget { alias, target } => {
|
||||
|
25
src/count.rs
Normal file
25
src/count.rs
Normal file
@ -0,0 +1,25 @@
|
||||
use crate::common::*;
|
||||
|
||||
pub struct Count<T: Display>(pub T, pub usize);
|
||||
|
||||
impl<T: Display> Display for Count<T> {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
if self.1 == 1 {
|
||||
write!(f, "{}", self.0)
|
||||
} else {
|
||||
write!(f, "{}s", self.0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn count() {
|
||||
assert_eq!(Count("dog", 0).to_string(), "dogs");
|
||||
assert_eq!(Count("dog", 1).to_string(), "dog");
|
||||
assert_eq!(Count("dog", 2).to_string(), "dogs");
|
||||
}
|
||||
}
|
3
src/default.rs
Normal file
3
src/default.rs
Normal file
@ -0,0 +1,3 @@
|
||||
pub(crate) fn default<T: Default>() -> T {
|
||||
Default::default()
|
||||
}
|
5
src/empty.rs
Normal file
5
src/empty.rs
Normal file
@ -0,0 +1,5 @@
|
||||
use crate::common::*;
|
||||
|
||||
pub(crate) fn empty<T, C: iter::FromIterator<T>>() -> C {
|
||||
iter::empty().collect()
|
||||
}
|
31
src/enclosure.rs
Normal file
31
src/enclosure.rs
Normal file
@ -0,0 +1,31 @@
|
||||
use crate::common::*;
|
||||
|
||||
pub struct Enclosure<T: Display> {
|
||||
enclosure: &'static str,
|
||||
value: T,
|
||||
}
|
||||
|
||||
impl<T: Display> Enclosure<T> {
|
||||
pub fn tick(value: T) -> Enclosure<T> {
|
||||
Enclosure {
|
||||
enclosure: "`",
|
||||
value,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Display> Display for Enclosure<T> {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
write!(f, "{}{}{}", self.enclosure, self.value, self.enclosure)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn tick() {
|
||||
assert_eq!(Enclosure::tick("foo").to_string(), "`foo`")
|
||||
}
|
||||
}
|
@ -22,6 +22,10 @@ mod compilation_error;
|
||||
mod compilation_error_kind;
|
||||
mod config;
|
||||
mod config_error;
|
||||
mod count;
|
||||
mod default;
|
||||
mod empty;
|
||||
mod enclosure;
|
||||
mod expression;
|
||||
mod fragment;
|
||||
mod function;
|
||||
@ -31,8 +35,8 @@ mod interrupt_guard;
|
||||
mod interrupt_handler;
|
||||
mod justfile;
|
||||
mod lexer;
|
||||
mod list;
|
||||
mod load_dotenv;
|
||||
mod misc;
|
||||
mod ordinal;
|
||||
mod output;
|
||||
mod output_error;
|
||||
@ -50,6 +54,7 @@ mod runtime_error;
|
||||
mod search;
|
||||
mod search_error;
|
||||
mod shebang;
|
||||
mod show_whitespace;
|
||||
mod state;
|
||||
mod string_literal;
|
||||
mod subcommand;
|
||||
@ -59,6 +64,7 @@ mod use_color;
|
||||
mod variables;
|
||||
mod verbosity;
|
||||
mod warning;
|
||||
mod write_message_context;
|
||||
|
||||
pub use crate::run::run;
|
||||
|
||||
|
123
src/list.rs
Normal file
123
src/list.rs
Normal file
@ -0,0 +1,123 @@
|
||||
use crate::common::*;
|
||||
|
||||
pub struct List<T: Display, I: Iterator<Item = T> + Clone> {
|
||||
conjunction: &'static str,
|
||||
values: I,
|
||||
}
|
||||
|
||||
impl<T: Display, I: Iterator<Item = T> + Clone> List<T, I> {
|
||||
pub fn or<II: IntoIterator<Item = T, IntoIter = I>>(values: II) -> List<T, I> {
|
||||
List {
|
||||
conjunction: "or",
|
||||
values: values.into_iter(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn and<II: IntoIterator<Item = T, IntoIter = I>>(values: II) -> List<T, I> {
|
||||
List {
|
||||
conjunction: "and",
|
||||
values: values.into_iter(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn or_ticked<II: IntoIterator<Item = T, IntoIter = I>>(
|
||||
values: II,
|
||||
) -> List<Enclosure<T>, impl Iterator<Item = Enclosure<T>> + Clone> {
|
||||
List::or(values.into_iter().map(Enclosure::tick))
|
||||
}
|
||||
|
||||
pub fn and_ticked<II: IntoIterator<Item = T, IntoIter = I>>(
|
||||
values: II,
|
||||
) -> List<Enclosure<T>, impl Iterator<Item = Enclosure<T>> + Clone> {
|
||||
List::and(values.into_iter().map(Enclosure::tick))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Display, I: Iterator<Item = T> + Clone> Display for List<T, I> {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
let mut values = self.values.clone().fuse();
|
||||
|
||||
if let Some(first) = values.next() {
|
||||
write!(f, "{}", first)?;
|
||||
} else {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let second = values.next();
|
||||
|
||||
if second.is_none() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let third = values.next();
|
||||
|
||||
if let (Some(second), None) = (second.as_ref(), third.as_ref()) {
|
||||
write!(f, " {} {}", self.conjunction, second)?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut current = second;
|
||||
let mut next = third;
|
||||
|
||||
loop {
|
||||
match (current, next) {
|
||||
(Some(c), Some(n)) => {
|
||||
write!(f, ", {}", c)?;
|
||||
current = Some(n);
|
||||
next = values.next();
|
||||
}
|
||||
(Some(c), None) => {
|
||||
write!(f, ", {} {}", self.conjunction, c)?;
|
||||
return Ok(());
|
||||
}
|
||||
_ => panic!("Iterator was fused, but returned Some after None"),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn or() {
|
||||
assert_eq!("1", List::or(&[1]).to_string());
|
||||
assert_eq!("1 or 2", List::or(&[1, 2]).to_string());
|
||||
assert_eq!("1, 2, or 3", List::or(&[1, 2, 3]).to_string());
|
||||
assert_eq!("1, 2, 3, or 4", List::or(&[1, 2, 3, 4]).to_string());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn and() {
|
||||
assert_eq!("1", List::and(&[1]).to_string());
|
||||
assert_eq!("1 and 2", List::and(&[1, 2]).to_string());
|
||||
assert_eq!("1, 2, and 3", List::and(&[1, 2, 3]).to_string());
|
||||
assert_eq!("1, 2, 3, and 4", List::and(&[1, 2, 3, 4]).to_string());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn or_ticked() {
|
||||
assert_eq!("`1`", List::or_ticked(&[1]).to_string());
|
||||
assert_eq!("`1` or `2`", List::or_ticked(&[1, 2]).to_string());
|
||||
assert_eq!("`1`, `2`, or `3`", List::or_ticked(&[1, 2, 3]).to_string());
|
||||
assert_eq!(
|
||||
"`1`, `2`, `3`, or `4`",
|
||||
List::or_ticked(&[1, 2, 3, 4]).to_string()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn and_ticked() {
|
||||
assert_eq!("`1`", List::and_ticked(&[1]).to_string());
|
||||
assert_eq!("`1` and `2`", List::and_ticked(&[1, 2]).to_string());
|
||||
assert_eq!(
|
||||
"`1`, `2`, and `3`",
|
||||
List::and_ticked(&[1, 2, 3]).to_string()
|
||||
);
|
||||
assert_eq!(
|
||||
"`1`, `2`, `3`, and `4`",
|
||||
List::and_ticked(&[1, 2, 3, 4]).to_string()
|
||||
);
|
||||
}
|
||||
}
|
167
src/misc.rs
167
src/misc.rs
@ -1,167 +0,0 @@
|
||||
use crate::common::*;
|
||||
|
||||
pub(crate) fn show_whitespace(text: &str) -> String {
|
||||
text
|
||||
.chars()
|
||||
.map(|c| match c {
|
||||
'\t' => '␉',
|
||||
' ' => '␠',
|
||||
_ => c,
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub(crate) fn default<T: Default>() -> T {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
pub(crate) fn empty<T, C: iter::FromIterator<T>>() -> C {
|
||||
iter::empty().collect()
|
||||
}
|
||||
|
||||
pub(crate) fn ticks<T: Display>(ts: &[T]) -> Vec<Tick<T>> {
|
||||
ts.iter().map(Tick).collect()
|
||||
}
|
||||
|
||||
pub(crate) fn maybe_s(n: usize) -> &'static str {
|
||||
if n == 1 {
|
||||
""
|
||||
} else {
|
||||
"s"
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn conjoin<T: Display>(
|
||||
f: &mut Formatter,
|
||||
values: &[T],
|
||||
conjunction: &str,
|
||||
) -> Result<(), fmt::Error> {
|
||||
match values.len() {
|
||||
0 => {}
|
||||
1 => write!(f, "{}", values[0])?,
|
||||
2 => write!(f, "{} {} {}", values[0], conjunction, values[1])?,
|
||||
_ => {
|
||||
for (i, item) in values.iter().enumerate() {
|
||||
write!(f, "{}", item)?;
|
||||
if i == values.len() - 1 {
|
||||
} else if i == values.len() - 2 {
|
||||
write!(f, ", {} ", conjunction)?;
|
||||
} else {
|
||||
write!(f, ", ")?
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn write_message_context(
|
||||
f: &mut Formatter,
|
||||
color: Color,
|
||||
text: &str,
|
||||
offset: usize,
|
||||
line: usize,
|
||||
column: usize,
|
||||
width: usize,
|
||||
) -> Result<(), fmt::Error> {
|
||||
let width = if width == 0 { 1 } else { width };
|
||||
|
||||
let line_number = line.ordinal();
|
||||
match text.lines().nth(line) {
|
||||
Some(line) => {
|
||||
let mut i = 0;
|
||||
let mut space_column = 0;
|
||||
let mut space_line = String::new();
|
||||
let mut space_width = 0;
|
||||
for c in line.chars() {
|
||||
if c == '\t' {
|
||||
space_line.push_str(" ");
|
||||
if i < column {
|
||||
space_column += 4;
|
||||
}
|
||||
if i >= column && i < column + width {
|
||||
space_width += 4;
|
||||
}
|
||||
} else {
|
||||
if i < column {
|
||||
space_column += UnicodeWidthChar::width(c).unwrap_or(0);
|
||||
}
|
||||
if i >= column && i < column + width {
|
||||
space_width += UnicodeWidthChar::width(c).unwrap_or(0);
|
||||
}
|
||||
space_line.push(c);
|
||||
}
|
||||
i += c.len_utf8();
|
||||
}
|
||||
let line_number_width = line_number.to_string().len();
|
||||
writeln!(f, "{0:1$} |", "", line_number_width)?;
|
||||
writeln!(f, "{} | {}", line_number, space_line)?;
|
||||
write!(f, "{0:1$} |", "", line_number_width)?;
|
||||
write!(
|
||||
f,
|
||||
" {0:1$}{2}{3:^<4$}{5}",
|
||||
"",
|
||||
space_column,
|
||||
color.prefix(),
|
||||
"",
|
||||
space_width,
|
||||
color.suffix()
|
||||
)?;
|
||||
}
|
||||
None => {
|
||||
if offset != text.len() {
|
||||
write!(
|
||||
f,
|
||||
"internal error: Error has invalid line number: {}",
|
||||
line_number
|
||||
)?
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) struct And<'a, T: 'a + Display>(pub(crate) &'a [T]);
|
||||
|
||||
impl<'a, T: Display> Display for And<'a, T> {
|
||||
fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
|
||||
conjoin(f, self.0, "and")
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct Or<'a, T: 'a + Display>(pub(crate) &'a [T]);
|
||||
|
||||
impl<'a, T: Display> Display for Or<'a, T> {
|
||||
fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
|
||||
conjoin(f, self.0, "or")
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct Tick<'a, T: 'a + Display>(pub(crate) &'a T);
|
||||
|
||||
impl<'a, T: Display> Display for Tick<'a, T> {
|
||||
fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
|
||||
write!(f, "`{}`", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn conjoin_or() {
|
||||
assert_eq!("1", Or(&[1]).to_string());
|
||||
assert_eq!("1 or 2", Or(&[1, 2]).to_string());
|
||||
assert_eq!("1, 2, or 3", Or(&[1, 2, 3]).to_string());
|
||||
assert_eq!("1, 2, 3, or 4", Or(&[1, 2, 3, 4]).to_string());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn conjoin_and() {
|
||||
assert_eq!("1", And(&[1]).to_string());
|
||||
assert_eq!("1 and 2", And(&[1, 2]).to_string());
|
||||
assert_eq!("1, 2, and 3", And(&[1, 2, 3]).to_string());
|
||||
assert_eq!("1, 2, 3, and 4", And(&[1, 2, 3, 4]).to_string());
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
use crate::common::*;
|
||||
|
||||
use crate::{interrupt_handler::InterruptHandler, misc::maybe_s};
|
||||
use crate::interrupt_handler::InterruptHandler;
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
|
||||
fn edit<P: AsRef<OsStr>>(path: P) -> Result<(), i32> {
|
||||
@ -285,10 +285,10 @@ pub fn run() -> Result<(), i32> {
|
||||
let min_arguments = recipe.min_arguments();
|
||||
if min_arguments > 0 {
|
||||
die!(
|
||||
"Recipe `{}` cannot be used as default recipe since it requires at least {} argument{}.",
|
||||
"Recipe `{}` cannot be used as default recipe since it requires at least {} {}.",
|
||||
recipe.name,
|
||||
min_arguments,
|
||||
maybe_s(min_arguments)
|
||||
Count("argument", min_arguments),
|
||||
);
|
||||
}
|
||||
vec![recipe.name]
|
||||
|
@ -1,7 +1,5 @@
|
||||
use crate::common::*;
|
||||
|
||||
use crate::misc::{maybe_s, ticks, write_message_context, And, Or, Tick};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum RuntimeError<'a> {
|
||||
ArgumentCountMismatch {
|
||||
@ -102,9 +100,9 @@ impl<'a> Display for RuntimeError<'a> {
|
||||
} => {
|
||||
write!(
|
||||
f,
|
||||
"Justfile does not contain recipe{} {}.",
|
||||
maybe_s(recipes.len()),
|
||||
Or(&ticks(recipes))
|
||||
"Justfile does not contain {} {}.",
|
||||
Count("recipe", recipes.len()),
|
||||
List::or_ticked(recipes),
|
||||
)?;
|
||||
if let Some(suggestion) = *suggestion {
|
||||
write!(f, "\nDid you mean `{}`?", suggestion)?;
|
||||
@ -113,9 +111,9 @@ impl<'a> Display for RuntimeError<'a> {
|
||||
UnknownOverrides { ref overrides } => {
|
||||
write!(
|
||||
f,
|
||||
"Variable{} {} overridden on the command line but not present in justfile",
|
||||
maybe_s(overrides.len()),
|
||||
And(&overrides.iter().map(Tick).collect::<Vec<_>>())
|
||||
"{} {} overridden on the command line but not present in justfile",
|
||||
Count("Variable", overrides.len()),
|
||||
List::and_ticked(overrides),
|
||||
)?;
|
||||
}
|
||||
ArgumentCountMismatch {
|
||||
@ -129,29 +127,29 @@ impl<'a> Display for RuntimeError<'a> {
|
||||
let expected = min;
|
||||
write!(
|
||||
f,
|
||||
"Recipe `{}` got {} argument{} but {}takes {}",
|
||||
"Recipe `{}` got {} {} but {}takes {}",
|
||||
recipe,
|
||||
found,
|
||||
maybe_s(found),
|
||||
Count("argument", found),
|
||||
if expected < found { "only " } else { "" },
|
||||
expected
|
||||
)?;
|
||||
} else if found < min {
|
||||
write!(
|
||||
f,
|
||||
"Recipe `{}` got {} argument{} but takes at least {}",
|
||||
"Recipe `{}` got {} {} but takes at least {}",
|
||||
recipe,
|
||||
found,
|
||||
maybe_s(found),
|
||||
Count("argument", found),
|
||||
min
|
||||
)?;
|
||||
} else if found > max {
|
||||
write!(
|
||||
f,
|
||||
"Recipe `{}` got {} argument{} but takes at most {}",
|
||||
"Recipe `{}` got {} {} but takes at most {}",
|
||||
recipe,
|
||||
found,
|
||||
maybe_s(found),
|
||||
Count("argument", found),
|
||||
max
|
||||
)?;
|
||||
}
|
||||
|
@ -1,6 +1,4 @@
|
||||
use std::{fmt, io, path::PathBuf};
|
||||
|
||||
use crate::misc::And;
|
||||
use crate::common::*;
|
||||
|
||||
pub(crate) enum SearchError {
|
||||
MultipleCandidates {
|
||||
@ -29,11 +27,10 @@ impl fmt::Display for SearchError {
|
||||
f,
|
||||
"Multiple candidate justfiles found in `{}`: {}",
|
||||
candidates[0].parent().unwrap().display(),
|
||||
And(
|
||||
&candidates
|
||||
List::and_ticked(
|
||||
candidates
|
||||
.iter()
|
||||
.map(|candidate| format!("`{}`", candidate.file_name().unwrap().to_string_lossy()))
|
||||
.collect::<Vec<String>>()
|
||||
.map(|candidate| candidate.file_name().unwrap().to_string_lossy())
|
||||
),
|
||||
),
|
||||
SearchError::NotFound => write!(f, "No justfile found"),
|
||||
|
18
src/show_whitespace.rs
Normal file
18
src/show_whitespace.rs
Normal file
@ -0,0 +1,18 @@
|
||||
use crate::common::*;
|
||||
|
||||
/// String wrapper that uses nonblank characters to display spaces and tabs
|
||||
pub struct ShowWhitespace<'str>(pub &'str str);
|
||||
|
||||
impl<'str> Display for ShowWhitespace<'str> {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
for c in self.0.chars() {
|
||||
match c {
|
||||
'\t' => write!(f, "␉")?,
|
||||
' ' => write!(f, "␠")?,
|
||||
_ => write!(f, "{}", c)?,
|
||||
};
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
@ -1,5 +1,4 @@
|
||||
use crate::common::*;
|
||||
use crate::misc::write_message_context;
|
||||
|
||||
use Warning::*;
|
||||
|
||||
|
67
src/write_message_context.rs
Normal file
67
src/write_message_context.rs
Normal file
@ -0,0 +1,67 @@
|
||||
use crate::common::*;
|
||||
|
||||
pub(crate) fn write_message_context(
|
||||
f: &mut Formatter,
|
||||
color: Color,
|
||||
text: &str,
|
||||
offset: usize,
|
||||
line: usize,
|
||||
column: usize,
|
||||
width: usize,
|
||||
) -> Result<(), fmt::Error> {
|
||||
let width = if width == 0 { 1 } else { width };
|
||||
|
||||
let line_number = line.ordinal();
|
||||
match text.lines().nth(line) {
|
||||
Some(line) => {
|
||||
let mut i = 0;
|
||||
let mut space_column = 0;
|
||||
let mut space_line = String::new();
|
||||
let mut space_width = 0;
|
||||
for c in line.chars() {
|
||||
if c == '\t' {
|
||||
space_line.push_str(" ");
|
||||
if i < column {
|
||||
space_column += 4;
|
||||
}
|
||||
if i >= column && i < column + width {
|
||||
space_width += 4;
|
||||
}
|
||||
} else {
|
||||
if i < column {
|
||||
space_column += UnicodeWidthChar::width(c).unwrap_or(0);
|
||||
}
|
||||
if i >= column && i < column + width {
|
||||
space_width += UnicodeWidthChar::width(c).unwrap_or(0);
|
||||
}
|
||||
space_line.push(c);
|
||||
}
|
||||
i += c.len_utf8();
|
||||
}
|
||||
let line_number_width = line_number.to_string().len();
|
||||
writeln!(f, "{0:1$} |", "", line_number_width)?;
|
||||
writeln!(f, "{} | {}", line_number, space_line)?;
|
||||
write!(f, "{0:1$} |", "", line_number_width)?;
|
||||
write!(
|
||||
f,
|
||||
" {0:1$}{2}{3:^<4$}{5}",
|
||||
"",
|
||||
space_column,
|
||||
color.prefix(),
|
||||
"",
|
||||
space_width,
|
||||
color.suffix()
|
||||
)?;
|
||||
}
|
||||
None => {
|
||||
if offset != text.len() {
|
||||
write!(
|
||||
f,
|
||||
"internal error: Error has invalid line number: {}",
|
||||
line_number
|
||||
)?
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
Loading…
Reference in New Issue
Block a user