Skip to content

Commit 4091ae5

Browse files
committed
Support box drawing characters
1 parent b2dd918 commit 4091ae5

File tree

4 files changed

+119
-130
lines changed

4 files changed

+119
-130
lines changed

benches/simple.rs

+15-14
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
#[macro_use]
22
extern crate criterion;
33

4-
use criterion::black_box;
5-
use criterion::Criterion;
4+
use criterion::{BatchSize, Criterion};
65

76
use annotate_snippets::*;
87
use std::ops::Range;
@@ -101,24 +100,26 @@ fn range_snippet() -> Snippet<'static, Range<usize>> {
101100

102101
pub fn criterion_benchmark(c: &mut Criterion) {
103102
c.bench_function("format [&str]", |b| {
104-
b.iter(|| {
105-
black_box({
103+
b.iter_batched_ref(
104+
|| Vec::<u8>::with_capacity(1100),
105+
|out| {
106106
let snippet = source_snippet();
107107
let formatted = format(&snippet, &());
108-
let mut out: Vec<u8> = Vec::new();
109-
renderer::Ascii::plain().render(&formatted, &(), &mut out)
110-
})
111-
})
108+
renderer::Ascii::new().render(&formatted, &(), out)
109+
},
110+
BatchSize::SmallInput,
111+
)
112112
});
113113
c.bench_function("format [Range]", |b| {
114-
b.iter(|| {
115-
black_box({
114+
b.iter_batched_ref(
115+
|| Vec::<u8>::with_capacity(1100),
116+
|out| {
116117
let snippet = range_snippet();
117118
let formatted = format(&snippet, &SOURCE);
118-
let mut out: Vec<u8> = Vec::new();
119-
renderer::Ascii::plain().render(&formatted, &SOURCE, &mut out)
120-
})
121-
})
119+
renderer::Ascii::new().render(&formatted, &SOURCE, out)
120+
},
121+
BatchSize::SmallInput,
122+
)
122123
});
123124
}
124125

examples/format.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,9 @@ fn main() {
6060
};
6161

6262
let formatted = format(&snippet, &());
63-
renderer::Ascii::ansi()
63+
renderer::Ascii::new()
64+
.ansi(true)
65+
.box_drawing(true)
6466
.render(&formatted, &(), &mut io::stdout().lock())
6567
.unwrap();
6668
}

src/input.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -218,8 +218,9 @@ pub struct Message<'s> {
218218
pub enum Level {
219219
/// Typically displayed using a red color.
220220
Error,
221-
/// Typically displayed using a blue color.
221+
/// Typically displayed using a yellow color.
222222
Warning,
223+
/// Typically displayed using a blue color.
223224
Info,
224225
Note,
225226
Help,

src/renderer/default.rs

+99-114
Original file line numberDiff line numberDiff line change
@@ -5,187 +5,164 @@ use crate::{
55
};
66
use std::io;
77

8+
#[derive(Debug, Copy, Clone, Default)]
89
pub struct Ascii {
9-
use_ansi: bool,
10+
pub ansi: bool,
11+
#[allow(unused)] // TODO
12+
pub fold: bool,
13+
pub box_drawing: bool,
14+
#[doc(hidden)] // to allow structural creation with `Ascii { ..Default::default() }`
15+
pub __non_exhaustive: (),
1016
}
1117

1218
impl Ascii {
13-
pub fn plain() -> Self {
14-
Ascii { use_ansi: false }
19+
pub fn new() -> Self {
20+
Default::default()
1521
}
1622

17-
pub fn ansi() -> Self {
18-
Ascii { use_ansi: true }
23+
pub fn ansi(&mut self, b: bool) -> &mut Self {
24+
self.ansi = b;
25+
self
26+
}
27+
28+
pub fn box_drawing(&mut self, b: bool) -> &mut Self {
29+
self.box_drawing = b;
30+
self
1931
}
2032
}
2133

2234
impl Ascii {
2335
#[inline(always)]
24-
fn reset(&self, w: &mut dyn io::Write) -> io::Result<()> {
25-
if self.use_ansi {
36+
fn reset(self, w: &mut dyn io::Write) -> io::Result<()> {
37+
if self.ansi {
2638
write!(w, "\x1B[0m")
2739
} else {
2840
Ok(())
2941
}
3042
}
3143

32-
fn bold(&self, w: &mut dyn io::Write) -> io::Result<()> {
33-
if self.use_ansi {
34-
write!(w, "\x1B[0;1m")
35-
} else {
36-
Ok(())
37-
}
38-
}
39-
40-
// fg(Fixed(9))
4144
#[inline(always)]
42-
fn bright_red(&self, w: &mut dyn io::Write) -> io::Result<()> {
43-
if self.use_ansi {
44-
write!(w, "\x1B[0;31;1m")
45-
} else {
46-
Ok(())
47-
}
48-
}
49-
50-
// bold + fg(Fixed(9))
51-
#[inline(always)]
52-
fn bold_bright_red(&self, w: &mut dyn io::Write) -> io::Result<()> {
53-
if self.use_ansi {
54-
write!(w, "\x1B[1;31;1m")
55-
} else {
56-
Ok(())
57-
}
58-
}
59-
60-
// fg(Fixed(11))
61-
#[inline(always)]
62-
fn bright_yellow(&self, w: &mut dyn io::Write) -> io::Result<()> {
63-
if self.use_ansi {
64-
write!(w, "\x1B[0;33;1m")
65-
} else {
66-
Ok(())
67-
}
68-
}
69-
70-
// bold + fg(Fixed(11))
71-
#[inline(always)]
72-
fn bold_bright_yellow(&self, w: &mut dyn io::Write) -> io::Result<()> {
73-
if self.use_ansi {
74-
write!(w, "\x1B[1;33;1m")
75-
} else {
76-
Ok(())
77-
}
78-
}
79-
80-
// fg(Fixed(12))
81-
#[inline(always)]
82-
fn bright_blue(&self, w: &mut dyn io::Write) -> io::Result<()> {
83-
if self.use_ansi {
84-
write!(w, "\x1B[0;34;1m")
45+
fn bold(self, w: &mut dyn io::Write) -> io::Result<()> {
46+
if self.ansi {
47+
write!(w, "\x1B[0;1m")
8548
} else {
8649
Ok(())
8750
}
8851
}
8952

9053
// bold + fg(Fixed(12))
9154
#[inline(always)]
92-
fn bold_bright_blue(&self, w: &mut dyn io::Write) -> io::Result<()> {
93-
if self.use_ansi {
55+
fn bold_bright_blue(self, w: &mut dyn io::Write) -> io::Result<()> {
56+
if self.ansi {
9457
write!(w, "\x1B[1;34;1m")
9558
} else {
9659
Ok(())
9760
}
9861
}
9962

100-
// fg(Fixed(14))
63+
// FIXME: emitted ANSI codes are highly redundant when repeated
10164
#[inline(always)]
102-
fn bright_cyan(&self, w: &mut dyn io::Write) -> io::Result<()> {
103-
if self.use_ansi {
104-
write!(w, "\x1B[0;36;1m")
65+
fn style_for(self, level: Level, w: &mut dyn io::Write) -> io::Result<()> {
66+
if self.ansi {
67+
match level {
68+
Level::Error => write!(w, "\x1B[0;31;1m"),
69+
Level::Warning => write!(w, "\x1B[0;33;1m"),
70+
Level::Info => write!(w, "\x1B[0;34;1m"),
71+
Level::Note => self.reset(w),
72+
Level::Help => write!(w, "\x1B[0;36;1m"),
73+
}
10574
} else {
10675
Ok(())
10776
}
10877
}
10978

110-
// bold + fg(Fixed(14))
79+
// FIXME: emitted ANSI codes are highly redundant when repeated
11180
#[inline(always)]
112-
fn bold_bright_cyan(&self, w: &mut dyn io::Write) -> io::Result<()> {
113-
if self.use_ansi {
114-
write!(w, "\x1B[1;36;1m")
81+
fn style_bold_for(self, level: Level, w: &mut dyn io::Write) -> io::Result<()> {
82+
if self.ansi {
83+
match level {
84+
Level::Error => write!(w, "\x1B[1;31;1m"),
85+
Level::Warning => write!(w, "\x1B[1;33;1m"),
86+
Level::Info => write!(w, "\x1B[1;34;1m"),
87+
Level::Note => self.reset(w),
88+
Level::Help => write!(w, "\x1B[1;36;1m"),
89+
}
11590
} else {
11691
Ok(())
11792
}
11893
}
119-
120-
// FIXME: emitted ANSI codes are highly redundant when repeated
121-
#[inline(always)]
122-
fn style_for(&self, level: Level, w: &mut dyn io::Write) -> io::Result<()> {
123-
match level {
124-
Level::Error => self.bright_red(w),
125-
Level::Warning => self.bright_yellow(w),
126-
Level::Info => self.bright_blue(w),
127-
Level::Note => self.reset(w),
128-
Level::Help => self.bright_cyan(w),
129-
}
130-
}
131-
132-
// FIXME: emitted ANSI codes are highly redundant when repeated
133-
#[inline(always)]
134-
fn style_bold_for(&self, level: Level, w: &mut dyn io::Write) -> io::Result<()> {
135-
match level {
136-
Level::Error => self.bold_bright_red(w),
137-
Level::Warning => self.bold_bright_yellow(w),
138-
Level::Info => self.bold_bright_blue(w),
139-
Level::Note => self.reset(w),
140-
Level::Help => self.bold_bright_cyan(w),
141-
}
142-
}
14394
}
14495

14596
impl Ascii {
146-
fn render_marks(&self, marks: &[Mark], w: &mut dyn io::Write) -> io::Result<()> {
97+
fn render_marks(self, marks: &[Mark], w: &mut dyn io::Write) -> io::Result<()> {
14798
for mark in marks {
14899
self.style_for(mark.level, w)?;
149-
let c = match mark.kind {
150-
MarkKind::Start => '/',
151-
MarkKind::Continue => '|',
152-
MarkKind::Here => '\\',
100+
let c = if self.box_drawing {
101+
match mark.kind {
102+
MarkKind::Start => '┌',
103+
MarkKind::Continue => '│',
104+
MarkKind::Here => '└',
105+
}
106+
} else {
107+
match mark.kind {
108+
MarkKind::Start => '/',
109+
MarkKind::Continue => '|',
110+
MarkKind::Here => '\\',
111+
}
153112
};
154113
write!(w, "{}", c)?;
155114
}
156115
self.reset(w)
157116
}
158117

159118
fn render_source_line<Span: crate::Span>(
160-
&self,
119+
self,
161120
line: &SourceLine<'_, Span>,
121+
is_long: bool,
162122
f: &dyn SpanWriter<Span>,
163123
w: &mut dyn io::Write,
164124
) -> io::Result<()> {
165125
match line {
166-
SourceLine::Content { span, subspan } => f.write(w, span, subspan),
126+
SourceLine::Content { span, subspan } => {
127+
write!(w, " ")?;
128+
f.write(w, span, subspan)
129+
}
167130
SourceLine::Annotation { message, underline } => {
168-
write!(w, "{:>width$}", "", width = underline.0)?;
169-
self.style_bold_for(message.map_or(Level::Info, |message| message.level), w)?;
170-
// FIXME: respect level for pointer character
171-
if underline.0 == 0 {
172-
write!(w, "{:_>width$} ", "^", width = underline.1)?;
131+
let (indent, len) = if is_long {
132+
(0, underline.0 + underline.1 + 1)
133+
} else {
134+
(underline.0 + 1, underline.1)
135+
};
136+
write!(w, "{:>width$}", "", width = indent)?;
137+
let level = message.map_or(Level::Info, |message| message.level);
138+
self.style_bold_for(level, w)?;
139+
if is_long {
140+
if self.box_drawing {
141+
write!(w, "{:─>width$} ", "┘", width = len)?;
142+
} else {
143+
write!(w, "{:_>width$} ", "^", width = len)?;
144+
}
173145
} else {
174-
write!(w, "{:->width$} ", "", width = underline.1)?;
146+
match level {
147+
Level::Error => write!(w, "{:^>width$} ", "", width = len)?,
148+
Level::Warning => write!(w, "{:~>width$} ", "", width = len)?,
149+
Level::Info | Level::Help | Level::Note => {
150+
write!(w, "{:->width$} ", "", width = len)?
151+
}
152+
}
175153
}
176154
write!(
177155
w,
178156
"{}",
179157
message.map_or(&"" as &dyn DebugAndDisplay, |message| message.text)
180-
)?;
181-
self.reset(w)
158+
)
182159
}
183160
SourceLine::Empty => Ok(()),
184161
}
185162
}
186163

187164
fn render_raw_line(
188-
&self,
165+
self,
189166
line: &RawLine<'_>,
190167
line_num_width: usize,
191168
w: &mut dyn io::Write,
@@ -194,7 +171,11 @@ impl Ascii {
194171
&RawLine::Origin { path, pos } => {
195172
write!(w, "{:>width$}", "", width = line_num_width)?;
196173
self.bold_bright_blue(w)?;
197-
write!(w, "-->")?;
174+
if self.box_drawing {
175+
write!(w, "═╦═")?;
176+
} else {
177+
write!(w, "-->")?;
178+
}
198179
self.reset(w)?;
199180
write!(w, " {}", path)?;
200181
if let Some((line, column)) = pos {
@@ -228,7 +209,7 @@ impl Ascii {
228209
write!(w, "[{}]", code)?;
229210
}
230211
self.bold(w)?;
231-
writeln!(w, ": {}", title.message.text)
212+
writeln!(w, ": {}", title.message.text)
232213
}
233214
}
234215
}
@@ -266,10 +247,11 @@ impl Renderer for Ascii {
266247
line,
267248
} => {
268249
self.bold_bright_blue(w)?;
250+
let sep = if self.box_drawing { '║' } else { '|' };
269251
if let Some(lineno) = lineno {
270-
write!(w, "{:>width$} | ", lineno, width = line_num_width)?;
252+
write!(w, "{:>width$} {} ", lineno, sep, width = line_num_width)?;
271253
} else {
272-
write!(w, "{:>width$} | ", "", width = line_num_width)?;
254+
write!(w, "{:>width$} {} ", "", sep, width = line_num_width)?;
273255
}
274256
self.reset(w)?;
275257
write!(
@@ -279,7 +261,10 @@ impl Renderer for Ascii {
279261
width = marks_width - inline_marks.len()
280262
)?;
281263
self.render_marks(inline_marks, w)?;
282-
self.render_source_line(line, f, w)?;
264+
let is_long = inline_marks
265+
.last()
266+
.map_or(false, |mark| mark.kind == MarkKind::Here);
267+
self.render_source_line(line, is_long, f, w)?;
283268
writeln!(w)
284269
}
285270
DisplayLine::Raw(line) => self.render_raw_line(line, line_num_width, w),

0 commit comments

Comments
 (0)