Skip to content

Commit def1f82

Browse files
feat: remove nerd fonts and centralize icon definition
1 parent c3ce85f commit def1f82

10 files changed

Lines changed: 221 additions & 33 deletions

File tree

Cargo.lock

Lines changed: 23 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ test-with = { version = "0.15", default-features = false, features = [] }
8484
rstest = { version = "0.25.0", default-features = false }
8585
rstest_reuse = "0.7.0"
8686
shell-quote = "0.7.2"
87+
strum = { version = "0.28.0", features = ["derive"] }
8788

8889
[workspace]
8990
members = [

src/cli/run/helpers/benchmark_display.rs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use crate::api_client::FetchLocalRunBenchmarkResult;
22
use crate::cli::run::helpers;
33
use crate::executor::ExecutorName;
4+
use crate::local_logger::icons::Icon;
45
use console::style;
56
use std::collections::HashMap;
67
use tabled::settings::object::{Columns, Rows};
@@ -87,9 +88,14 @@ struct MemoryRow {
8788
alloc_calls: String,
8889
}
8990

90-
fn build_table_with_style<T: Tabled>(rows: &[T], instrument: &str, icon: &str) -> String {
91+
fn build_table_with_style<T: Tabled>(rows: &[T], instrument: &str, icon: Icon) -> String {
9192
// Line after panel header: use ┬ to connect with columns below
92-
let header_line = HorizontalLine::full('─', '┬', '├', '┤');
93+
let header_line = HorizontalLine::full(
94+
Icon::BoxHorizontal.as_char(),
95+
Icon::BoxTDown.as_char(),
96+
Icon::BoxTRight.as_char(),
97+
Icon::BoxTLeft.as_char(),
98+
);
9399
// Line after column headers: keep intersection
94100
let column_line = HorizontalLine::inherit(Style::modern());
95101

@@ -104,7 +110,7 @@ fn build_table_with_style<T: Tabled>(rows: &[T], instrument: &str, icon: &str) -
104110
.with(
105111
Style::rounded()
106112
.remove_horizontals()
107-
.intersection_top('─')
113+
.intersection_top(Icon::BoxHorizontal.as_char())
108114
.horizontals([(1, header_line), (2, column_line)]),
109115
)
110116
.with(Modify::new(Rows::first()).with(Alignment::center()))

src/cli/shared.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use crate::VERSION;
22
use crate::executor::config::SimulationTool;
33
use crate::local_logger::CODSPEED_U8_COLOR_CODE;
4+
use crate::local_logger::icons::Icon;
45
use crate::prelude::*;
56
use crate::run_environment::interfaces::RepositoryProvider;
67
use crate::runner_mode::{RunnerMode, load_shell_session_mode};
@@ -19,7 +20,7 @@ pub(crate) fn show_banner() {
1920

2021
let version_tag = style(format!("v{VERSION}")).bold();
2122
let url = style("codspeed.io").color256(CODSPEED_U8_COLOR_CODE);
22-
let separator = style("─".repeat(52)).dim();
23+
let separator = style(Icon::Separator.to_string().repeat(52)).dim();
2324

2425
eprintln!();
2526
eprintln!("{}", style(logo).color256(CODSPEED_U8_COLOR_CODE).bold());

src/executor/interfaces.rs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use crate::local_logger::icons::Icon;
12
use serde::{Deserialize, Serialize};
23
use std::fmt;
34

@@ -29,12 +30,12 @@ impl ExecutorName {
2930
}
3031
}
3132

32-
/// Nerd Font icon for this executor.
33-
pub fn icon(&self) -> &'static str {
33+
/// Icon for this executor.
34+
pub fn icon(&self) -> Icon {
3435
match self {
35-
ExecutorName::Valgrind => "\u{f4bc}",
36-
ExecutorName::WallTime => "\u{f520}",
37-
ExecutorName::Memory => "\u{efc5}",
36+
ExecutorName::Valgrind => Icon::ExecutorValgrind,
37+
ExecutorName::WallTime => Icon::ExecutorWallTime,
38+
ExecutorName::Memory => Icon::ExecutorMemory,
3839
}
3940
}
4041
}

src/local_logger/icons.rs

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
use std::fmt;
2+
3+
#[derive(Debug)]
4+
#[cfg_attr(test, derive(strum::EnumIter))]
5+
pub enum Icon {
6+
// Log level prefixes
7+
/// Group header prefix (›)
8+
GroupArrow,
9+
/// Task completion checkmark (✓)
10+
Checkmark,
11+
/// Error indicator (✗)
12+
Error,
13+
/// Warning indicator (⚠)
14+
Warning,
15+
/// Debug bullet point (·)
16+
Bullet,
17+
18+
// Impact / result indicators
19+
/// Impact improved / up arrow (↑)
20+
ImpactUp,
21+
/// Impact regressed / down arrow (↓)
22+
ImpactDown,
23+
/// Impact neutral / filled circle (●)
24+
ImpactNeutral,
25+
/// No impact data / empty circle (○)
26+
ImpactNoData,
27+
28+
// Executor icons
29+
/// CPU simulation executor (⚙)
30+
ExecutorValgrind,
31+
/// Walltime executor (⏱)
32+
ExecutorWallTime,
33+
/// Memory executor (▤)
34+
ExecutorMemory,
35+
36+
// Box-drawing (used by rolling buffer)
37+
/// Top-left arc corner (╭)
38+
BoxTopLeft,
39+
/// Top-right arc corner (╮)
40+
BoxTopRight,
41+
/// Bottom-left arc corner (╰)
42+
BoxBottomLeft,
43+
/// Bottom-right arc corner (╯)
44+
BoxBottomRight,
45+
/// Horizontal line (─)
46+
BoxHorizontal,
47+
/// Vertical line (│)
48+
BoxVertical,
49+
/// T-junction pointing down (┬)
50+
BoxTDown,
51+
/// T-junction pointing right (├)
52+
BoxTRight,
53+
/// T-junction pointing left (┤)
54+
BoxTLeft,
55+
56+
// Miscellaneous
57+
/// Horizontal separator line character (─), same glyph as BoxHorizontal
58+
Separator,
59+
/// Truncation ellipsis (…)
60+
Ellipsis,
61+
}
62+
63+
impl Icon {
64+
/// Return the icon as a `char`. Panics if the icon's string is not a single character
65+
/// (all current icons are single codepoints, so this is always safe).
66+
pub fn as_char(&self) -> char {
67+
self.to_string()
68+
.chars()
69+
.next()
70+
.expect("icon must be a single character")
71+
}
72+
}
73+
74+
impl fmt::Display for Icon {
75+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
76+
let ch = match self {
77+
Icon::GroupArrow => "\u{203a}", // ›
78+
Icon::Checkmark => "\u{2713}", // ✓
79+
Icon::Error => "\u{2717}", // ✗
80+
Icon::Warning => "\u{26a0}", // ⚠
81+
Icon::Bullet => "\u{00b7}", // ·
82+
Icon::ImpactUp => "\u{2191}", // ↑
83+
Icon::ImpactDown => "\u{2193}", // ↓
84+
Icon::ImpactNeutral => "\u{25cf}", // ●
85+
Icon::ImpactNoData => "\u{25cb}", // ○
86+
Icon::ExecutorValgrind => "\u{2699}", // ⚙
87+
Icon::ExecutorWallTime => "\u{23f1}", // ⏱
88+
Icon::ExecutorMemory => "\u{25a4}", // ▤
89+
Icon::BoxTopLeft => "\u{256d}", // ╭
90+
Icon::BoxTopRight => "\u{256e}", // ╮
91+
Icon::BoxBottomLeft => "\u{2570}", // ╰
92+
Icon::BoxBottomRight => "\u{256f}", // ╯
93+
Icon::BoxHorizontal => "\u{2500}", // ─
94+
Icon::BoxVertical => "\u{2502}", // │
95+
Icon::BoxTDown => "\u{252c}", // ┬
96+
Icon::BoxTRight => "\u{251c}", // ├
97+
Icon::BoxTLeft => "\u{2524}", // ┤
98+
Icon::Separator => "\u{2500}", // ─
99+
Icon::Ellipsis => "\u{2026}", // …
100+
};
101+
f.write_str(ch)
102+
}
103+
}
104+
105+
#[cfg(test)]
106+
mod tests {
107+
use strum::IntoEnumIterator;
108+
109+
use super::*;
110+
111+
#[test]
112+
fn test_icon_rendering() {
113+
let rendered = Icon::iter()
114+
.map(|icon| format!("{icon:?}: {icon}"))
115+
.collect::<Vec<_>>()
116+
.join("\n");
117+
118+
insta::assert_snapshot!(rendered);
119+
}
120+
}

src/local_logger/mod.rs

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
pub mod icons;
12
pub mod rolling_buffer;
23

34
use std::{
@@ -15,6 +16,7 @@ use std::io::Write;
1516
use std::sync::LazyLock;
1617

1718
use crate::logger::{GroupEvent, JsonEvent, get_group_event, get_json_event};
19+
use icons::Icon;
1820

1921
pub const CODSPEED_U8_COLOR_CODE: u8 = 208; // #FF8700
2022

@@ -167,7 +169,9 @@ impl Log for LocalLogger {
167169

168170
/// Format a group header with styled prefix
169171
fn format_group_header(name: &str) -> String {
170-
let prefix = style("\u{f0da}").color256(CODSPEED_U8_COLOR_CODE).bold();
172+
let prefix = style(Icon::GroupArrow.to_string())
173+
.color256(CODSPEED_U8_COLOR_CODE)
174+
.bold();
171175
let title = style(name).bold();
172176
format!("{prefix} {title}")
173177
}
@@ -179,7 +183,11 @@ pub(crate) fn format_checkmark(label: &str, dim: bool) -> String {
179183
} else {
180184
label.to_string()
181185
};
182-
format!(" {} {}", style("\u{f00c}").green().bold(), label)
186+
format!(
187+
" {} {}",
188+
style(Icon::Checkmark.to_string()).green().bold(),
189+
label
190+
)
183191
}
184192

185193
/// Format elapsed duration in a compact human-readable way
@@ -229,13 +237,13 @@ fn print_record(record: &log::Record) {
229237
fn format_log(level: log::Level, message: &str, target: &str) -> String {
230238
match level {
231239
log::Level::Error => {
232-
let prefix = style("\u{f00d}").red().bold();
240+
let prefix = style(Icon::Error.to_string()).red().bold();
233241
let msg = indent_lines(message, " ");
234242
let msg = Style::new().red().apply_to(msg);
235243
format!(" {prefix} {msg}")
236244
}
237245
log::Level::Warn => {
238-
let prefix = style("\u{f071}").yellow();
246+
let prefix = style(Icon::Warning.to_string()).yellow();
239247
let msg = indent_lines(message, " ");
240248
let msg = Style::new().yellow().apply_to(msg);
241249
format!(" {prefix} {msg}")
@@ -246,7 +254,7 @@ fn format_log(level: log::Level, message: &str, target: &str) -> String {
246254
format!(" {msg}")
247255
}
248256
log::Level::Debug => {
249-
let prefix = style("\u{00B7}").dim();
257+
let prefix = style(Icon::Bullet.to_string()).dim();
250258
let msg = indent_lines(message, " ");
251259
let msg = Style::new().blue().dim().apply_to(msg);
252260
format!(" {prefix} {msg}")

src/local_logger/rolling_buffer/mod.rs

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use std::time::{Duration, Instant};
55

66
use super::{
77
CODSPEED_U8_COLOR_CODE, IS_TTY, SPINNER, SPINNER_TICKS, TICK_INTERVAL_MS, format_checkmark,
8+
icons::Icon,
89
};
910
use console::{Term, style};
1011
use std::sync::LazyLock;
@@ -113,7 +114,7 @@ impl RollingBuffer {
113114
let title_styled = style(&self.title).color256(CODSPEED_U8_COLOR_CODE);
114115

115116
let line = format!(" {tick_styled} {title_styled}");
116-
console::truncate_str(&line, self.term_width, "…").into_owned()
117+
console::truncate_str(&line, self.term_width, &Icon::Ellipsis.to_string()).into_owned()
117118
}
118119

119120
fn render_top_delimiter(&self) -> String {
@@ -126,18 +127,18 @@ impl RollingBuffer {
126127
} else {
127128
String::new()
128129
};
129-
let prefix = format!("{INDENT}\u{256d}\u{2500}");
130-
let suffix = "\u{256e}"; // ╮
130+
let prefix = format!("{INDENT}{}{}", Icon::BoxTopLeft, Icon::BoxHorizontal);
131+
let suffix = Icon::BoxTopRight.to_string();
131132
let label_visible_len = if truncated > 0 {
132133
format!(" {truncated} lines above ").len()
133134
} else {
134135
0
135136
};
136137
let used = console::measure_text_width(&prefix)
137138
+ label_visible_len
138-
+ console::measure_text_width(suffix);
139+
+ console::measure_text_width(&suffix);
139140
let remaining = self.term_width.saturating_sub(used);
140-
let bar = "\u{2500}".repeat(remaining);
141+
let bar = Icon::BoxHorizontal.to_string().repeat(remaining);
141142
format!(
142143
"{}{}{}",
143144
style(prefix.to_string()).dim(),
@@ -147,22 +148,22 @@ impl RollingBuffer {
147148
}
148149

149150
fn render_bottom_delimiter(&self) -> String {
150-
let prefix = format!("{INDENT}\u{2570}");
151-
let suffix = "\u{256f}"; // ╯
152-
let used = console::measure_text_width(&prefix) + console::measure_text_width(suffix);
151+
let prefix = format!("{INDENT}{}", Icon::BoxBottomLeft);
152+
let suffix = Icon::BoxBottomRight.to_string();
153+
let used = console::measure_text_width(&prefix) + console::measure_text_width(&suffix);
153154
let remaining = self.term_width.saturating_sub(used);
154-
let bar = "\u{2500}".repeat(remaining);
155+
let bar = Icon::BoxHorizontal.to_string().repeat(remaining);
155156
format!("{}", style(format!("{prefix}{bar}{suffix}")).dim())
156157
}
157158

158159
fn render_content_line(&self, line: &str) -> String {
159-
let inner_indent = format!("{INDENT}\u{2502} ");
160-
let right_border = "\u{2502}"; // │
160+
let inner_indent = format!("{INDENT}{} ", Icon::BoxVertical);
161+
let right_border = Icon::BoxVertical.to_string();
161162
let chrome_width =
162-
console::measure_text_width(&inner_indent) + console::measure_text_width(right_border);
163+
console::measure_text_width(&inner_indent) + console::measure_text_width(&right_border);
163164
let max_content_width = self.term_width.saturating_sub(chrome_width);
164165
let truncated = if max_content_width > 0 {
165-
console::truncate_str(line, max_content_width, "…")
166+
console::truncate_str(line, max_content_width, &Icon::Ellipsis.to_string())
166167
} else {
167168
std::borrow::Cow::Borrowed("")
168169
};
@@ -173,7 +174,7 @@ impl RollingBuffer {
173174
style(&inner_indent).dim(),
174175
style(&*truncated).dim(),
175176
" ".repeat(padding),
176-
style(right_border).dim()
177+
style(&right_border).dim()
177178
)
178179
}
179180

0 commit comments

Comments
 (0)