const MIN_STRING: usize = 10;
/// Describes the layout of a piece of text.
-pub struct StringFormat<'a> {
+pub(crate) struct StringFormat<'a> {
/// The opening sequence of characters for the piece of text
- pub opener: &'a str,
+ pub(crate) opener: &'a str,
/// The closing sequence of characters for the piece of text
- pub closer: &'a str,
+ pub(crate) closer: &'a str,
/// The opening sequence of characters for a line
- pub line_start: &'a str,
+ pub(crate) line_start: &'a str,
/// The closing sequence of characters for a line
- pub line_end: &'a str,
+ pub(crate) line_end: &'a str,
/// The allocated box to fit the text into
- pub shape: Shape,
+ pub(crate) shape: Shape,
/// Trim trailing whitespaces
- pub trim_end: bool,
- pub config: &'a Config,
+ pub(crate) trim_end: bool,
+ pub(crate) config: &'a Config,
}
impl<'a> StringFormat<'a> {
- pub fn new(shape: Shape, config: &'a Config) -> StringFormat<'a> {
+ pub(crate) fn new(shape: Shape, config: &'a Config) -> StringFormat<'a> {
StringFormat {
opener: "\"",
closer: "\"",
/// This allows to fit more graphemes from the string on a line when
/// SnippetState::EndWithLineFeed.
fn max_width_without_indent(&self) -> Option<usize> {
- Some(self.config.max_width().checked_sub(self.line_end.len())?)
+ self.config.max_width().checked_sub(self.line_end.len())
}
}
-pub fn rewrite_string<'a>(
+pub(crate) fn rewrite_string<'a>(
orig: &str,
fmt: &StringFormat<'a>,
newline_max_chars: usize,
if is_new_line(grapheme) {
// take care of blank lines
result = trim_end_but_line_feed(fmt.trim_end, result);
- result.push_str("\n");
+ result.push('\n');
if !is_bareline_ok && cur_start + i + 1 < graphemes.len() {
result.push_str(&indent_without_newline);
result.push_str(fmt.line_start);
wrap_str(result, fmt.config.max_width(), fmt.shape)
}
-/// Returns the index to the end of the URL if the given string includes an
-/// URL or alike. Otherwise, returns `None`;
+/// Returns the index to the end of the URL if the split at index of the given string includes a
+/// URL or alike. Otherwise, returns `None`.
fn detect_url(s: &[&str], index: usize) -> Option<usize> {
let start = match s[..=index].iter().rposition(|g| is_whitespace(g)) {
Some(pos) => pos + 1,
None => 0,
};
+ // 8 = minimum length for a string to contain a URL
if s.len() < start + 8 {
return None;
}
- let prefix = s[start..start + 8].concat();
- if prefix.starts_with("https://")
- || prefix.starts_with("http://")
- || prefix.starts_with("ftp://")
- || prefix.starts_with("file://")
+ let split = s[start..].concat();
+ if split.contains("https://")
+ || split.contains("http://")
+ || split.contains("ftp://")
+ || split.contains("file://")
{
match s[index..].iter().position(|g| is_whitespace(g)) {
Some(pos) => Some(index + pos - 1),
}
cur_index
};
+ if max_width_index_in_input == 0 {
+ return SnippetState::EndOfInput(input.concat());
+ }
// Find the position in input for breaking the string
if line_end.is_empty()
return if trim_end {
SnippetState::LineEnd(input[..=url_index_end].concat(), index_plus_ws + 1)
} else {
- return SnippetState::LineEnd(input[..=index_plus_ws].concat(), index_plus_ws + 1);
+ SnippetState::LineEnd(input[..=index_plus_ws].concat(), index_plus_ws + 1)
};
}
// Found a whitespace and what is on its left side is big enough.
Some(index) if index >= MIN_STRING => break_at(index),
// No whitespace found, try looking for a punctuation instead
- _ => match input[0..max_width_index_in_input]
- .iter()
- .rposition(|grapheme| is_punctuation(grapheme))
+ _ => match (0..max_width_index_in_input)
+ .rev()
+ .skip_while(|pos| !is_valid_linebreak(input, *pos))
+ .next()
{
// Found a punctuation and what is on its left side is big enough.
Some(index) if index >= MIN_STRING => break_at(index),
// Either no boundary character was found to the left of `input[max_chars]`, or the line
// got too small. We try searching for a boundary character to the right.
- _ => match input[max_width_index_in_input..]
- .iter()
- .position(|grapheme| is_whitespace(grapheme) || is_punctuation(grapheme))
+ _ => match (max_width_index_in_input..input.len())
+ .skip_while(|pos| !is_valid_linebreak(input, *pos))
+ .next()
{
// A boundary was found after the line limit
- Some(index) => break_at(max_width_index_in_input + index),
+ Some(index) => break_at(index),
// No boundary to the right, the input cannot be broken
None => SnippetState::EndOfInput(input.concat()),
},
}
}
+fn is_valid_linebreak(input: &[&str], pos: usize) -> bool {
+ let is_whitespace = is_whitespace(input[pos]);
+ if is_whitespace {
+ return true;
+ }
+ let is_punctuation = is_punctuation(input[pos]);
+ if is_punctuation && !is_part_of_type(input, pos) {
+ return true;
+ }
+ false
+}
+
+fn is_part_of_type(input: &[&str], pos: usize) -> bool {
+ input.get(pos..=pos + 1) == Some(&[":", ":"])
+ || input.get(pos.saturating_sub(1)..=pos) == Some(&[":", ":"])
+}
+
fn is_new_line(grapheme: &str) -> bool {
let bytes = grapheme.as_bytes();
bytes.starts_with(b"\n") || bytes.starts_with(b"\r\n")
rewrite_string("eq_", &fmt, 2);
}
+ #[test]
+ fn line_break_at_valid_points_test() {
+ let string = "[TheName](Dont::break::my::type::That::would::be::very::nice) break here";
+ let graphemes = UnicodeSegmentation::graphemes(&*string, false).collect::<Vec<&str>>();
+ assert_eq!(
+ break_string(20, false, "", &graphemes[..]),
+ SnippetState::LineEnd(
+ "[TheName](Dont::break::my::type::That::would::be::very::nice) ".to_string(),
+ 62
+ )
+ );
+ }
+
#[test]
fn should_break_on_whitespace() {
let string = "Placerat felis. Mauris porta ante sagittis purus.";