Skip to content

Commit 58e9c74

Browse files
committed
Implement inline ANSI highlighting in summary.
1 parent 5d442a8 commit 58e9c74

File tree

6 files changed

+262
-93
lines changed

6 files changed

+262
-93
lines changed
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
// Copyright 2023 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
//! Utilities to style matcher explanation message with ANSI formatting.
16+
17+
use std::fmt::Display;
18+
use std::io::IsTerminal;
19+
20+
21+
/// Environment variable controlling the usage of ansi color in difference
22+
/// summary.
23+
pub(crate) const NO_COLOR_VAR: &str = "GTEST_RUST_NO_COLOR";
24+
25+
// Use ANSI code to enable styling on the summary lines.
26+
//
27+
// See https://en.wikipedia.org/wiki/ANSI_escape_code.
28+
pub(crate) struct LineStyle {
29+
ansi_prefix: &'static str,
30+
ansi_suffix: &'static str,
31+
highlighted_prefix: &'static str,
32+
header: char,
33+
}
34+
35+
impl LineStyle {
36+
// Font in red and bold
37+
pub(crate) fn extra_actual_style() -> Self {
38+
Self {
39+
ansi_prefix: "\x1B[1;31m",
40+
ansi_suffix: "\x1B[0m",
41+
highlighted_prefix: "",
42+
header: '-',
43+
}
44+
}
45+
46+
// Font in green and bold
47+
pub(crate) fn extra_expected_style() -> Self {
48+
Self {
49+
ansi_prefix: "\x1B[1;32m",
50+
ansi_suffix: "\x1B[0m",
51+
highlighted_prefix: "",
52+
header: '+',
53+
}
54+
}
55+
56+
// Font in italic
57+
pub(crate) fn comment_style() -> Self {
58+
Self { ansi_prefix: "\x1B[3m", ansi_suffix: "\x1B[0m", highlighted_prefix: "", header: ' ' }
59+
}
60+
61+
// No ansi styling
62+
pub(crate) fn unchanged_style() -> Self {
63+
Self { ansi_prefix: "", ansi_suffix: "", highlighted_prefix: "", header: ' ' }
64+
}
65+
66+
pub(crate) fn style(self, line: &str) -> impl Display + '_ {
67+
StyledLine { style: self, line }
68+
}
69+
70+
pub(crate) fn style_highlighted(self, line: HighlightedString) -> impl Display {
71+
StyledLineWithHighlight { style: self, line }
72+
}
73+
}
74+
75+
struct StyledLine<'a> {
76+
style: LineStyle,
77+
line: &'a str,
78+
}
79+
80+
impl<'a> Display for StyledLine<'a> {
81+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
82+
if stdout_supports_color() {
83+
write!(
84+
f,
85+
"{}{}{}{}",
86+
self.style.header, self.style.ansi_prefix, self.line, self.style.ansi_suffix
87+
)
88+
} else {
89+
write!(f, "{}{}", self.style.header, self.line)
90+
}
91+
}
92+
}
93+
struct StyledLineWithHighlight {
94+
style: LineStyle,
95+
line: HighlightedString,
96+
}
97+
98+
impl Display for StyledLineWithHighlight {
99+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
100+
write!(f, "{}", self.style.header)?;
101+
for (piece, highlight) in &self.line.0 {
102+
if ! stdout_supports_color() {
103+
write!(f, "{}", piece)?;
104+
} else if *highlight {
105+
write!(f, "{}{}{}", self.style.highlighted_prefix, piece, self.style.ansi_suffix)?;
106+
} else {
107+
write!(f, "{}{}{}", self.style.ansi_prefix, piece, self.style.ansi_suffix)?;
108+
}
109+
}
110+
Ok(())
111+
}
112+
}
113+
114+
pub(crate) struct HighlightedString(Vec<(String, bool)>);
115+
116+
impl HighlightedString {
117+
pub(crate) fn new() -> Self {
118+
Self(vec![])
119+
}
120+
121+
pub(crate) fn push_highlighted(&mut self, c: char) {
122+
match self.0.last_mut() {
123+
Some((s, true)) => s.push(c),
124+
_ => self.0.push((c.to_string(), true)),
125+
}
126+
}
127+
128+
pub(crate) fn push(&mut self, c: char) {
129+
match self.0.last_mut() {
130+
Some((s, false)) => s.push(c),
131+
_ => self.0.push((c.to_string(), false)),
132+
}
133+
}
134+
}
135+
136+
137+
138+
#[rustversion::since(1.70)]
139+
fn stdout_supports_color() -> bool {
140+
match (is_env_var_set("NO_COLOR"), is_env_var_set("FORCE_COLOR")) {
141+
(true, _) => false,
142+
(false, true) => true,
143+
(false, false) => std::io::stdout().is_terminal(),
144+
}
145+
}
146+
147+
#[rustversion::not(since(1.70))]
148+
fn stdout_supports_color() -> bool {
149+
is_env_var_set("FORCE_COLOR")
150+
}
151+
152+
fn is_env_var_set(var: &'static str) -> bool {
153+
std::env::var(var).map(|s| !s.is_empty()).unwrap_or(false)
154+
}

googletest/src/matcher_support/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,5 +21,6 @@
2121
pub(crate) mod count_elements;
2222
pub mod description;
2323
pub(crate) mod edit_distance;
24+
pub(crate) mod line_style;
2425
pub(crate) mod summarize_diff;
2526
pub(crate) mod zipped_iterator;

0 commit comments

Comments
 (0)