use syntect::highlighting::Style as SyntectStyle;
use unicode_segmentation::UnicodeSegmentation;
use crate::config::Config;
use crate::delta::State;
use crate::features::line_numbers;
use crate::features::side_by_side::line_is_too_long;
use crate::features::side_by_side::LeftRight;
use crate::features::side_by_side::PanelSide::*;
use crate::style::Style;
use super::{line_numbers::SideBySideLineWidth, side_by_side::available_line_width};
#[derive(Clone)]
pub struct WrapConfig {
pub wrap_symbol: String,
pub wrap_right_wrap_symbol: String,
pub wrap_right_prefix_symbol: String,
// In fractions of 1000 so that a >100 wide panel can
// still be configured down to a single character.
pub use_wrap_right_permille: usize,
pub max_lines: usize,
}
// Wrap the given `line` if it is longer than `line_width`. Wrap to at most
// `wrap_config.max_lines` lines, then truncate again. Place `wrap_config.wrap_symbol`
// at then end of wrapped lines. However if wrapping results in only one extra line
// and if the width of the wrapped line is less than `wrap_config.use_wrap_right_permille`
// right-align the second line and use `wrap_config.wrap_right_symbol`.
//
// The input `line` is expected to start with an (ultimately not printed) "+/-/ " prefix.
// A prefix ("_") is also added to the start of wrapped lines.
pub fn wrap_line<'a, I, S>(
config: &'a Config,
line: I,
line_width: usize,
fill_style: &S,
inline_hint_style: &Option<S>,
) -> Vec<Vec<(S, &'a str)>>
where
I: IntoIterator<Item = (S, &'a str)> + std::fmt::Debug,
<I as IntoIterator>::IntoIter: DoubleEndedIterator,
S: Copy + Default + std::fmt::Debug,
{
let mut result = Vec::new();
let wrap_config = &config.wrap_config;
// Symbol which:
// - represents the additional "+/-/ " prefix on the unwrapped input line, its
// length is added to the line_width.
// - can be more prominent than a space because syntax highlighting has already
// been done.
// - is added to the beginning of wrapped lines so the wrapped lines also have
// a prefix (which is not printed).
const LINEPREFIX: &str = "_";
static_assertions::const_assert_eq!(LINEPREFIX.len(), 1); // must be a 1-byte char
let max_len = line_width + LINEPREFIX.len();
let mut curr_line = Vec::new();
let mut curr_len = 0;
// Determine the background (diff) and color (syntax) of an inserted symbol.
let symbol_style = match inline_hint_style {
Some(style) => *style,
None => *fill_style,
};
let mut stack = line.into_iter().rev().collect::<Vec<_>>();
let line_limit_reached = |result: &Vec<_>| {
// If only the wrap symbol and no extra text fits then wrapping is not possible.
let max_lines = if line_width > 1 {
wrap_config.max_lines
} else {
1
};
max_lines > 0 && result.len() + 1 >= max_lines
};
while !stack.is_empty() && !line_limit_reached(&result) && max_len > LINEPREFIX.len() {
let (style, text, graphemes) = stack
.pop()
.map(|(style, text)| (style, text, text.grapheme_indices(true).collect::<Vec<_>>()))
.unwrap();
let new_sum = curr_len + graphemes.len();
let must_split = if new_sum < max_len {
curr_line.push((style, text));
curr_len = new_sum;
false
} else if new_sum == max_len {
match stack.last() {
// Perfect fit, no need to make space for a `wrap_symbol`.
None => {
curr_line.push((style, text));
curr_len = new_sum;
false
}
// A single '\n' left on the stack can be pushed onto the current line.
Some((next_style, nl)) if stack.len() == 1 && *nl == "\n" => {
curr_line.push((style, text));
curr_line.push((*next_style, *nl));