use crate::highlighting::{Color, Style, StyleModifier};
#[cfg(feature = "parsing")]
use crate::parsing::ScopeStackOp;
use std::fmt::Write;
use std::ops::Range;
#[inline]
fn blend_fg_color(fg: Color, bg: Color) -> Color {
if fg.a == 0xff {
return fg;
}
let ratio = fg.a as u32;
let r = (fg.r as u32 * ratio + bg.r as u32 * (255 - ratio)) / 255;
let g = (fg.g as u32 * ratio + bg.g as u32 * (255 - ratio)) / 255;
let b = (fg.b as u32 * ratio + bg.b as u32 * (255 - ratio)) / 255;
Color {
r: r as u8,
g: g as u8,
b: b as u8,
a: 255,
}
}
pub fn as_24_bit_terminal_escaped(v: &[(Style, &str)], bg: bool) -> String {
let mut s: String = String::new();
for &(ref style, text) in v.iter() {
if bg {
write!(s,
"\x1b[48;2;{};{};{}m",
style.background.r,
style.background.g,
style.background.b)
.unwrap();
}
let fg = blend_fg_color(style.foreground, style.background);
write!(s, "\x1b[38;2;{};{};{}m{}", fg.r, fg.g, fg.b, text).unwrap();
}
s
}
const LATEX_REPLACE: [(&str, &str); 3] = [
("\\", "\\\\"),
("{", "\\{"),
("}", "\\}"),
];
pub fn as_latex_escaped(v: &[(Style, &str)]) -> String {
let mut s: String = String::new();
let mut prev_style: Option<Style> = None;
let mut content: String;
fn textcolor(style: &Style, first: bool) -> String {
format!("{}\\textcolor[RGB]{{{},{},{}}}{{",
if first { "" } else { "}" },
style.foreground.r,
style.foreground.b,
style.foreground.g)
}
for &(style, text) in v.iter() {
if let Some(ps) = prev_style {
match text {
" " => {
s.push(' ');
continue;
},
"\n" => continue,
_ => (),
}
if style != ps {
write!(s, "{}", textcolor(&style, false)).unwrap();
}
} else {
write!(s, "{}", textcolor(&style, true)).unwrap();
}
content = text.to_string();
for &(old, new) in LATEX_REPLACE.iter() {
content = content.replace(&old, new);
}
write!(s, "{}", &content).unwrap();
prev_style = Some(style);
}
s.push('}');
s
}
#[cfg(feature = "parsing")]
pub fn debug_print_ops(line: &str, ops: &[(usize, ScopeStackOp)]) {
for &(i, ref op) in ops.iter() {
println!("{}", line.trim_end());
print!("{: <1$}", "", i);
match *op {
ScopeStackOp::Push(s) => {
println!("^ +{}", s);
}
ScopeStackOp::Pop(count) => {
println!("^ pop {}", count);
}
ScopeStackOp::Clear(amount) => {
println!("^ clear {:?}", amount);
}
ScopeStackOp::Restore => println!("^ restore"),
ScopeStackOp::Noop => println!("noop"),
}
}
}
pub struct LinesWithEndings<'a> {
input: &'a str,
}
impl<'a> LinesWithEndings<'a> {
pub fn from(input: &'a str) -> LinesWithEndings<'a> {
LinesWithEndings { input }
}
}
impl<'a> Iterator for LinesWithEndings<'a> {
type Item = &'a str;
#[inline]
fn next(&mut self) -> Option<&'a str> {
if self.input.is_empty() {
return None;
}
let split = self.input
.find('\n')
.map(|i| i + 1)
.unwrap_or_else(|| self.input.len());
let (line, rest) = self.input.split_at(split);
self.input = rest;
Some(line)
}
}
#[allow(clippy::type_complexity)]
pub fn split_at<'a, A: Clone>(v: &[(A, &'a str)], split_i: usize) -> (Vec<(A, &'a str)>, Vec<(A, &'a str)>) {
let mut rest = v;
let mut rest_split_i = split_i;
let mut before = Vec::new();
for tok in rest { if tok.1.len() > rest_split_i {
break;
}
before.push(tok.clone());
rest_split_i -= tok.1.len();
}
rest = &rest[before.len()..];
let mut after = Vec::new();
if !rest.is_empty() && rest_split_i > 0 {
let (sa, sb) = rest[0].1.split_at(rest_split_i);
before.push((rest[0].0.clone(), sa));
after.push((rest[0].0.clone(), sb));
rest = &rest[1..];
}
after.extend_from_slice(rest);
(before, after)
}
pub fn modify_range<'a>(v: &[(Style, &'a str)], r: Range<usize>, modifier: StyleModifier) -> Vec<(Style, &'a str)> {
let (mut result, in_and_after) = split_at(v, r.start);
let (inside, mut after) = split_at(&in_and_after, r.end - r.start);
result.extend(inside.iter().map(|(style, s)| { (style.apply(modifier), *s)}));
result.append(&mut after);
result
}
#[cfg(test)]
mod tests {
use super::*;
use crate::highlighting::FontStyle;
#[test]
fn test_lines_with_endings() {
fn lines(s: &str) -> Vec<&str> {
LinesWithEndings::from(s).collect()
}
assert!(lines("").is_empty());
assert_eq!(lines("f"), vec!["f"]);
assert_eq!(lines("foo"), vec!["foo"]);
assert_eq!(lines("foo\n"), vec!["foo\n"]);
assert_eq!(lines("foo\nbar"), vec!["foo\n", "bar"]);
assert_eq!(lines("foo\nbar\n"), vec!["foo\n", "bar\n"]);
assert_eq!(lines("foo\r\nbar"), vec!["foo\r\n", "bar"]);
assert_eq!(lines("foo\r\nbar\r\n"), vec!["foo\r\n", "bar\r\n"]);
assert_eq!(lines("\nfoo"), vec!["\n", "foo"]);
assert_eq!(lines("\n\n\n"), vec!["\n", "\n", "\n"]);
}
#[test]
fn test_split_at() {
let l: &[(u8, &str)] = &[];
let (before, after) = split_at(l, 0); assert_eq!((&before[..], &after[..]), (&[][..],&[][..]));
let l = &[(0u8, "abc"), (1u8, "def"), (2u8, "ghi")];
let (before, after) = split_at(l, 0); assert_eq!((&before[..], &after[..]), (&[][..],&[(0u8, "abc"), (1u8, "def"), (2u8, "ghi")][..]));
let (before, after) = split_at(l, 4); assert_eq!((&before[..], &after[..]), (&[(0u8, "abc"), (1u8, "d")][..],&[(1u8, "ef"), (2u8, "ghi")][..]));
let (before, after) = split_at(l, 3); assert_eq!((&before[..], &after[..]), (&[(0u8, "abc")][..],&[(1u8, "def"), (2u8, "ghi")][..]));
let (before, after) = split_at(l, 9); assert_eq!((&before[..], &after[..]), (&[(0u8, "abc"), (1u8, "def"), (2u8, "ghi")][..], &[][..]));
let (before, after) = split_at(l, 10); assert_eq!((&before[..], &after[..]), (&[(0u8, "abc"), (1u8, "def"), (2u8, "ghi")][..], &[][..]));
}
#[test]
fn test_as_24_bit_terminal_escaped() {
let style = Style {
foreground: Color::WHITE,
background: Color::BLACK,
font_style: FontStyle::default(),
};
let s = as_24_bit_terminal_escaped(&[(style, "hello")], true);
assert_eq!(s, "\x1b[48;2;0;0;0m\x1b[38;2;255;255;255mhello");
let s = as_24_bit_terminal_escaped(&[(style, "hello")], false);
assert_eq!(s, "\x1b[38;2;255;255;255mhello");
let mut foreground = Color::WHITE;
foreground.a = 128;
let style = Style {
foreground,
background: Color::BLACK,
font_style: FontStyle::default(),
};
let s = as_24_bit_terminal_escaped(&[(style, "hello")], true);
assert_eq!(s, "\x1b[48;2;0;0;0m\x1b[38;2;128;128;128mhello");
}
}