use std::{
io::{self, Write},
iter::once,
marker::PhantomData,
ops::Range,
slice::Iter,
};
use crossterm::{
cursor::{MoveToColumn, MoveToNextLine},
style::{
Attribute, Color, Print, PrintStyledContent, SetAttribute, SetBackgroundColor, Stylize,
},
terminal::{Clear, ClearType},
QueueableCommand,
};
use super::{
unicode::{consume, spans_from_indices, truncate, Processor, Span},
ELLIPSIS,
};
pub struct SpannedLines<'a> {
iter: Iter<'a, Range<usize>>,
spans: &'a [Span],
}
impl<'a> Iterator for SpannedLines<'a> {
type Item = &'a [Span];
#[inline]
fn next(&mut self) -> Option<Self::Item> {
match self.iter.next() {
Some(rg) => Some(&self.spans[rg.start..rg.end]),
None => None,
}
}
}
pub trait KeepLines {
fn from_offset(offset: u16) -> Self;
fn subslice<'a>(&self, lines: &'a [Range<usize>]) -> &'a [Range<usize>];
}
pub struct Tail(usize);
impl KeepLines for Tail {
fn subslice<'a>(&self, lines: &'a [Range<usize>]) -> &'a [Range<usize>] {
&lines[lines.len() - self.0..]
}
fn from_offset(offset: u16) -> Self {
Self(offset as usize)
}
}
pub struct Head(usize);
impl KeepLines for Head {
fn subslice<'a>(&self, lines: &'a [Range<usize>]) -> &'a [Range<usize>] {
&lines[..self.0]
}
fn from_offset(offset: u16) -> Self {
Self(offset as usize)
}
}
struct All;
impl KeepLines for All {
fn subslice<'a>(&self, lines: &'a [Range<usize>]) -> &'a [Range<usize>] {
lines
}
fn from_offset(_: u16) -> Self {
Self
}
}
#[derive(Debug)]
pub struct Spanned<'a, P> {
rendered: &'a str,
spans: &'a [Span],
lines: &'a [Range<usize>],
_marker: PhantomData<P>,
}
impl<'a, P: Processor> Spanned<'a, P> {
#[inline]
pub fn new<L: KeepLines>(
indices: &[u32],
rendered: &'a str,
spans: &'a mut Vec<Span>,
lines: &'a mut Vec<Range<usize>>,
keep_lines: L,
) -> Self {
spans_from_indices::<P>(indices, rendered, spans, lines);
Self {
rendered,
spans,
lines: keep_lines.subslice(lines),
_marker: PhantomData,
}
}
#[inline]
fn max_line_bytes(&self) -> usize {
let mut max_line_bytes = 0;
for line in self.lines() {
if !line.is_empty() {
max_line_bytes = max_line_bytes
.max(line.last().unwrap().range.end - line.first().unwrap().range.start);
}
}
max_line_bytes
}
#[inline]
fn required_width(&self) -> usize {
let mut required_width = 0;
for line in self.lines() {
if let Some(span) = line.iter().rev().find(|span| span.is_match) {
required_width = required_width.max(
P::width(&self.rendered[line[0].range.start..span.range.end]),
);
}
}
required_width
}
#[inline]
fn required_offset(&self, max_width: u16, highlight_padding: u16) -> usize {
match (self.required_width() + highlight_padding as usize).checked_sub(max_width as usize) {
None | Some(0) => 0,
Some(mut offset) => {
let mut is_sharp = false;
for line in self.lines() {
if let Some(span) = line.iter().find(|span| span.is_match) {
let no_highlight_width =
P::width(&self.rendered[line[0].range.start..span.range.start]);
if no_highlight_width <= offset {
offset = no_highlight_width;
is_sharp = true;
}
}
}
if !is_sharp {
offset += 1;
};
if offset == 1 {
0
} else {
offset
}
}
}
}
#[inline]
fn start_line<W: Write>(stderr: &mut W, selected: bool) -> Result<(), io::Error> {
if selected {
stderr
.queue(SetAttribute(Attribute::Bold))?
.queue(SetBackgroundColor(Color::DarkGrey))?
.queue(PrintStyledContent("▌ ".magenta()))?;
} else {
stderr.queue(Print(" "))?;
}
Ok(())
}
#[inline]
fn print_span<W: Write>(
stderr: &mut W,
to_print: &str,
highlight: bool,
) -> Result<(), io::Error> {
if highlight {
stderr.queue(PrintStyledContent(to_print.cyan()))?;
} else {
stderr.queue(Print(to_print))?;
}
Ok(())
}
#[inline]
fn finish_line<W: Write>(stderr: &mut W) -> Result<(), io::Error> {
stderr
.queue(SetAttribute(Attribute::Reset))?
.queue(Clear(ClearType::UntilNewLine))?
.queue(MoveToNextLine(1))?;
Ok(())
}
#[inline]
pub fn queue_print<W: Write>(
&self,
stderr: &mut W,
selected: bool,
max_width: u16,
highlight_padding: u16,
) -> Result<(), io::Error> {
if self.max_line_bytes() <= max_width.saturating_sub(highlight_padding) as usize {
for line in self.lines() {
Self::start_line(stderr, selected)?;
for span in line {
Self::print_span(stderr, self.index_in(span), span.is_match)?;
}
Self::finish_line(stderr)?;
}
} else {
let offset = self.required_offset(max_width, highlight_padding);
for line in self.lines() {
Self::start_line(stderr, selected)?;
self.queue_print_line(stderr, line, offset, max_width)?;
Self::finish_line(stderr)?;
}
}
Ok(())
}
#[inline]
fn queue_print_line<W: Write>(
&self,
stderr: &mut W,
line: &[Span],
offset: usize,
capacity: u16,
) -> Result<(), io::Error> {
let mut remaining_capacity = capacity;
if line.is_empty() || remaining_capacity == 0 {
return Ok(());
};
if offset > 0 {
remaining_capacity -= 1;
stderr.queue(Print(ELLIPSIS))?;
};
let first_span = &line[0];
let (init, alignment) = consume::<P>(self.index_in(first_span), offset);
let new_first_span = Span {
range: first_span.range.start + init..first_span.range.end,
is_match: first_span.is_match,
};
match (remaining_capacity as usize).checked_sub(alignment) {
Some(new) => {
remaining_capacity = new as u16;
for _ in 0..alignment {
stderr.queue(Print(ELLIPSIS))?;
}
}
None => return Ok(()),
}
for span in once(&new_first_span).chain(line[1..].iter()) {
let substr = self.index_in(span);
match truncate::<P>(substr, remaining_capacity) {
Ok(new) => {
remaining_capacity = new;
Self::print_span(stderr, substr, span.is_match)?;
}
Err((prefix, alignment)) => {
Self::print_span(stderr, prefix, span.is_match)?;
if alignment > 0 {
for _ in 0..alignment {
stderr.queue(Print(ELLIPSIS))?;
}
} else {
let undo_width = P::last_grapheme_width(
&self.rendered[..span.range.start + prefix.len()],
);
stderr.queue(MoveToColumn(2 + capacity - undo_width as u16))?;
for _ in 0..undo_width {
stderr.queue(Print(ELLIPSIS))?;
}
}
return Ok(());
}
}
}
Ok(())
}
#[inline]
fn index_in(&self, span: &Span) -> &str {
&self.rendered[span.range.start..span.range.end]
}
#[inline]
fn lines(&self) -> SpannedLines<'_> {
SpannedLines {
iter: self.lines.iter(),
spans: self.spans,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::term::unicode::{is_ascii_safe, is_unicode_safe, AsciiProcessor, UnicodeProcessor};
#[test]
fn test_required_width() {
fn assert_correct_width(indices: Vec<u32>, rendered: &str, expected_width: usize) {
let mut spans = Vec::new();
let mut lines = Vec::new();
let spanned: Spanned<'_, UnicodeProcessor> =
Spanned::new(&indices, rendered, &mut spans, &mut lines, All);
if is_unicode_safe(rendered) {
assert_eq!(spanned.required_width(), expected_width);
}
if is_ascii_safe(rendered) {
let spanned: Spanned<'_, AsciiProcessor> =
Spanned::new(&indices, rendered, &mut spans, &mut lines, All);
assert_eq!(spanned.required_width(), expected_width);
}
}
assert_correct_width(vec![], "a", 0);
assert_correct_width(vec![0], "a", 1);
assert_correct_width(vec![1], "ab", 2);
assert_correct_width(vec![0], "Hb", 2);
assert_correct_width(vec![1], "Hb", 3);
assert_correct_width(vec![0, 4], "ab\ncd", 2);
assert_correct_width(vec![0, 4], "ab\nHd", 3);
assert_correct_width(vec![0, 5], "ab\n\nHH", 4);
assert_correct_width(vec![1, 5], "HHb\n\nab", 4);
}
#[test]
fn test_required_offset() {
fn assert_correct_offset(
indices: Vec<u32>,
rendered: &str,
max_width: u16,
expected_offset: usize,
) {
let mut spans = Vec::new();
let mut lines = Vec::new();
if is_unicode_safe(rendered) {
let spanned: Spanned<'_, UnicodeProcessor> =
Spanned::new(&indices, rendered, &mut spans, &mut lines, All);
assert_eq!(spanned.required_offset(max_width, 0), expected_offset);
}
if is_ascii_safe(rendered) {
let spanned: Spanned<'_, AsciiProcessor> =
Spanned::new(&indices, rendered, &mut spans, &mut lines, All);
assert_eq!(spanned.required_offset(max_width, 0), expected_offset);
}
}
assert_correct_offset(vec![], "a", 1, 0);
assert_correct_offset(vec![], "abc", 1, 0);
assert_correct_offset(vec![2], "abc", 1, 2);
assert_correct_offset(vec![2], "abc", 2, 2);
assert_correct_offset(vec![2], "abc", 3, 0);
assert_correct_offset(vec![2], "abc\nab", 2, 2);
assert_correct_offset(vec![7], "abc\nabcd", 2, 3);
assert_correct_offset(vec![7], "abc\nabcd", 2, 3);
assert_correct_offset(vec![0, 7], "abc\nabcd", 2, 0);
assert_correct_offset(vec![1, 7], "abc\nabcd", 2, 0);
assert_correct_offset(vec![2, 7], "abc\nabcd", 2, 2);
assert_correct_offset(vec![0, 6], "abc\naHd", 2, 0);
assert_correct_offset(vec![1, 6], "abc\naHd", 2, 0);
assert_correct_offset(vec![2, 6], "abc\naHd", 2, 2);
assert_correct_offset(vec![2, 6], "abc\naHd", 3, 2);
assert_correct_offset(vec![2, 4, 8], "abc\na\r\naHd", 1, 0);
assert_correct_offset(vec![2, 4, 8], "abc\na\r\naHd", 2, 0);
assert_correct_offset(vec![2, 8], "abc\na\r\naHd", 2, 2);
assert_correct_offset(vec![2, 4, 8], "abc\na\r\naHd", 3, 0);
assert_correct_offset(vec![2, 8], "abc\na\r\naHd", 3, 2);
assert_correct_offset(vec![2, 8], "abc\na\r\naHd", 4, 0);
}
}