}
}
-pub fn rewrite_string<'a>(orig: &str, fmt: &StringFormat<'a>) -> Option<String> {
+pub fn rewrite_string<'a>(
+ orig: &str,
+ fmt: &StringFormat<'a>,
+ newline_max_chars: usize,
+) -> Option<String> {
let max_chars_with_indent = fmt.max_chars_with_indent()?;
let max_chars_without_indent = fmt.max_chars_without_indent()?;
let indent_with_newline = fmt.shape.indent.to_string_with_newline(fmt.config);
}
// The input starting at cur_start needs to be broken
- match break_string(cur_max_chars, fmt.trim_end, &graphemes[cur_start..]) {
+ match break_string(
+ cur_max_chars,
+ fmt.trim_end,
+ fmt.line_end,
+ &graphemes[cur_start..],
+ ) {
SnippetState::LineEnd(line, len) => {
result.push_str(&line);
result.push_str(fmt.line_end);
result.push_str(&indent_with_newline);
result.push_str(fmt.line_start);
- cur_max_chars = max_chars_with_indent;
+ cur_max_chars = newline_max_chars;
cur_start += len;
}
SnippetState::EndWithLineFeed(line, len) => {
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;
+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,
+ };
+ 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://")
+ {
+ match s[index..].iter().position(|g| is_whitespace(g)) {
+ Some(pos) => Some(index + pos - 1),
+ None => Some(s.len() - 1),
+ }
+ } else {
+ None
+ }
+}
+
/// Trims whitespaces to the right except for the line feed character.
fn trim_right_but_line_feed(trim_end: bool, result: String) -> String {
let whitespace_except_line_feed = |c: char| c.is_whitespace() && c != '\n';
EndWithLineFeed(String, usize),
}
+fn not_whitespace_except_line_feed(g: &str) -> bool {
+ is_line_feed(g) || !is_whitespace(g)
+}
+
/// Break the input string at a boundary character around the offset `max_chars`. A boundary
/// character is either a punctuation or a whitespace.
-fn break_string(max_chars: usize, trim_end: bool, input: &[&str]) -> SnippetState {
+fn break_string(max_chars: usize, trim_end: bool, line_end: &str, input: &[&str]) -> SnippetState {
let break_at = |index /* grapheme at index is included */| {
// Take in any whitespaces to the left/right of `input[index]` while
// preserving line feeds
- let not_whitespace_except_line_feed = |g| is_line_feed(g) || !is_whitespace(g);
let index_minus_ws = input[0..=index]
.iter()
.rposition(|grapheme| not_whitespace_except_line_feed(grapheme))
for (i, grapheme) in input[0..=index].iter().enumerate() {
if is_line_feed(grapheme) {
if i <= index_minus_ws {
- let mut line = input[0..i].join("");
+ let mut line = &input[0..i].concat()[..];
if trim_end {
- line = line.trim_right().to_string();
+ line = line.trim_right();
}
return SnippetState::EndWithLineFeed(format!("{}\n", line), i + 1);
}
for (i, grapheme) in input[index + 1..].iter().enumerate() {
if !trim_end && is_line_feed(grapheme) {
return SnippetState::EndWithLineFeed(
- input[0..=index + 1 + i].join("").to_string(),
+ input[0..=index + 1 + i].concat(),
index + 2 + i,
);
} else if not_whitespace_except_line_feed(grapheme) {
}
if trim_end {
- SnippetState::LineEnd(
- input[0..=index_minus_ws].join("").to_string(),
- index_plus_ws + 1,
- )
+ SnippetState::LineEnd(input[0..=index_minus_ws].concat(), index_plus_ws + 1)
} else {
- SnippetState::LineEnd(
- input[0..=index_plus_ws].join("").to_string(),
- index_plus_ws + 1,
- )
+ SnippetState::LineEnd(input[0..=index_plus_ws].concat(), index_plus_ws + 1)
}
};
// Find the position in input for breaking the string
+ if line_end.is_empty()
+ && trim_end
+ && !is_whitespace(input[max_chars - 1])
+ && is_whitespace(input[max_chars])
+ {
+ // At a breaking point already
+ // The line won't invalidate the rewriting because:
+ // - no extra space needed for the line_end character
+ // - extra whitespaces to the right can be trimmed
+ return break_at(max_chars - 1);
+ }
+ if let Some(url_index_end) = detect_url(input, max_chars) {
+ let index_plus_ws = url_index_end + input[url_index_end..]
+ .iter()
+ .skip(1)
+ .position(|grapheme| not_whitespace_except_line_feed(grapheme))
+ .unwrap_or(0);
+ 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);
+ };
+ }
match input[0..max_chars]
.iter()
.rposition(|grapheme| is_whitespace(grapheme))
// A boundary was found after the line limit
Some(index) => break_at(max_chars + index),
// No boundary to the right, the input cannot be broken
- None => SnippetState::EndOfInput(input.join("").to_string()),
+ None => SnippetState::EndOfInput(input.concat()),
},
},
}
#[cfg(test)]
mod test {
- use super::{break_string, rewrite_string, SnippetState, StringFormat};
+ use super::{break_string, detect_url, rewrite_string, SnippetState, StringFormat};
use config::Config;
use shape::{Indent, Shape};
use unicode_segmentation::UnicodeSegmentation;
fn issue343() {
let config = Default::default();
let fmt = StringFormat::new(Shape::legacy(2, Indent::empty()), &config);
- rewrite_string("eq_", &fmt);
+ rewrite_string("eq_", &fmt, 2);
}
#[test]
let string = "Placerat felis. Mauris porta ante sagittis purus.";
let graphemes = UnicodeSegmentation::graphemes(&*string, false).collect::<Vec<&str>>();
assert_eq!(
- break_string(20, false, &graphemes[..]),
+ break_string(20, false, "", &graphemes[..]),
SnippetState::LineEnd("Placerat felis. ".to_string(), 16)
);
assert_eq!(
- break_string(20, true, &graphemes[..]),
+ break_string(20, true, "", &graphemes[..]),
SnippetState::LineEnd("Placerat felis.".to_string(), 16)
);
}
let string = "Placerat_felis._Mauris_porta_ante_sagittis_purus.";
let graphemes = UnicodeSegmentation::graphemes(&*string, false).collect::<Vec<&str>>();
assert_eq!(
- break_string(20, false, &graphemes[..]),
+ break_string(20, false, "", &graphemes[..]),
SnippetState::LineEnd("Placerat_felis.".to_string(), 15)
);
}
let string = "Venenatis_tellus_vel_tellus. Aliquam aliquam dolor at justo.";
let graphemes = UnicodeSegmentation::graphemes(&*string, false).collect::<Vec<&str>>();
assert_eq!(
- break_string(20, false, &graphemes[..]),
+ break_string(20, false, "", &graphemes[..]),
SnippetState::LineEnd("Venenatis_tellus_vel_tellus. ".to_string(), 29)
);
assert_eq!(
- break_string(20, true, &graphemes[..]),
+ break_string(20, true, "", &graphemes[..]),
SnippetState::LineEnd("Venenatis_tellus_vel_tellus.".to_string(), 29)
);
}
let string = "Venenatis_tellus_vel_tellus";
let graphemes = UnicodeSegmentation::graphemes(&*string, false).collect::<Vec<&str>>();
assert_eq!(
- break_string(20, false, &graphemes[..]),
+ break_string(20, false, "", &graphemes[..]),
SnippetState::EndOfInput("Venenatis_tellus_vel_tellus".to_string())
);
}
let string = "Neque in sem. \n Pellentesque tellus augue.";
let graphemes = UnicodeSegmentation::graphemes(&*string, false).collect::<Vec<&str>>();
assert_eq!(
- break_string(15, false, &graphemes[..]),
+ break_string(15, false, "", &graphemes[..]),
SnippetState::EndWithLineFeed("Neque in sem. \n".to_string(), 20)
);
assert_eq!(
- break_string(25, false, &graphemes[..]),
+ break_string(25, false, "", &graphemes[..]),
SnippetState::EndWithLineFeed("Neque in sem. \n".to_string(), 20)
);
- // if `StringFormat::line_end` is true, then the line feed does not matter anymore
+
assert_eq!(
- break_string(15, true, &graphemes[..]),
- SnippetState::LineEnd("Neque in sem.".to_string(), 26)
+ break_string(15, true, "", &graphemes[..]),
+ SnippetState::LineEnd("Neque in sem.".to_string(), 19)
);
assert_eq!(
- break_string(25, true, &graphemes[..]),
- SnippetState::LineEnd("Neque in sem.".to_string(), 26)
+ break_string(25, true, "", &graphemes[..]),
+ SnippetState::EndWithLineFeed("Neque in sem.\n".to_string(), 20)
);
}
let string = "Neque in sem. Pellentesque tellus augue.";
let graphemes = UnicodeSegmentation::graphemes(&*string, false).collect::<Vec<&str>>();
assert_eq!(
- break_string(20, false, &graphemes[..]),
+ break_string(20, false, "", &graphemes[..]),
SnippetState::LineEnd("Neque in sem. ".to_string(), 25)
);
assert_eq!(
- break_string(20, true, &graphemes[..]),
+ break_string(20, true, "", &graphemes[..]),
SnippetState::LineEnd("Neque in sem.".to_string(), 25)
);
}
let graphemes = UnicodeSegmentation::graphemes(&*string, false).collect::<Vec<&str>>();
assert_eq!(
- break_string(25, false, &graphemes[..]),
+ break_string(25, false, "", &graphemes[..]),
SnippetState::EndWithLineFeed("Nulla\n".to_string(), 6)
);
assert_eq!(
- break_string(25, true, &graphemes[..]),
+ break_string(25, true, "", &graphemes[..]),
SnippetState::EndWithLineFeed("Nulla\n".to_string(), 6)
);
let mut config: Config = Default::default();
config.set().max_width(27);
let fmt = StringFormat::new(Shape::legacy(25, Indent::empty()), &config);
- let rewritten_string = rewrite_string(string, &fmt);
+ let rewritten_string = rewrite_string(string, &fmt, 27);
assert_eq!(
rewritten_string,
Some("\"Nulla\nconsequat erat at massa. \\\n Vivamus id mi.\"".to_string())
let mut fmt = StringFormat::new(Shape::legacy(25, Indent::empty()), &config);
fmt.trim_end = true;
- let rewritten_string = rewrite_string(string, &fmt);
+ let rewritten_string = rewrite_string(string, &fmt, 25);
assert_eq!(rewritten_string, Some("\"Vivamus id mi.\"".to_string()));
fmt.trim_end = false; // default value of trim_end
- let rewritten_string = rewrite_string(string, &fmt);
+ let rewritten_string = rewrite_string(string, &fmt, 25);
assert_eq!(rewritten_string, Some("\"Vivamus id mi. \"".to_string()));
}
config: &config,
};
- let rewritten_string = rewrite_string(string, &fmt);
+ let rewritten_string = rewrite_string(string, &fmt, 100);
assert_eq!(
rewritten_string,
Some("Vivamus id mi.\n // Vivamus id mi.".to_string())
};
assert_eq!(
- rewrite_string(comment, &fmt),
+ rewrite_string(comment, &fmt, 30),
Some(
"Aenean metus.\n // Vestibulum ac lacus. Vivamus\n // porttitor"
.to_string()
};
assert_eq!(
- rewrite_string(comment, &fmt),
+ rewrite_string(comment, &fmt, 30),
Some(
"Aenean metus.\n // Vestibulum ac lacus. Vivamus@\n // porttitor"
.to_string()
let comment = "Aenean metus. Vestibulum\n\nac lacus. Vivamus porttitor";
assert_eq!(
- rewrite_string(comment, &fmt),
+ rewrite_string(comment, &fmt, 30),
Some(
"Aenean metus. Vestibulum\n //\n // ac lacus. Vivamus porttitor".to_string()
)
fmt.shape = Shape::legacy(15, Indent::from_width(&config, 4));
let comment = "Aenean\n\nmetus. Vestibulum ac lacus. Vivamus porttitor";
assert_eq!(
- rewrite_string(comment, &fmt),
+ rewrite_string(comment, &fmt, 15),
Some(
r#"Aenean
//
let comment = "Aenean\n\nmetus. Vestibulum ac lacus.\n\n";
assert_eq!(
- rewrite_string(comment, &fmt),
+ rewrite_string(comment, &fmt, 20),
Some(
"Aenean\n //\n // metus. Vestibulum ac\n // lacus.\n //\n".to_string()
)
let comment = "Aenean\n\nmetus. Vestibulum ac lacus.\n";
assert_eq!(
- rewrite_string(comment, &fmt),
+ rewrite_string(comment, &fmt, 20),
Some("Aenean\n //\n // metus. Vestibulum ac\n // lacus.\n".to_string())
);
let comment = "Aenean\n \nmetus. Vestibulum ac lacus.";
assert_eq!(
- rewrite_string(comment, &fmt),
+ rewrite_string(comment, &fmt, 20),
Some("Aenean\n //\n // metus. Vestibulum ac\n // lacus.".to_string())
);
}
+
+ #[test]
+ fn boundary_on_edge() {
+ let config: Config = Default::default();
+ let mut fmt = StringFormat {
+ opener: "",
+ closer: "",
+ line_start: "// ",
+ line_end: "",
+ shape: Shape::legacy(13, Indent::from_width(&config, 4)),
+ trim_end: true,
+ config: &config,
+ };
+
+ let comment = "Aenean metus. Vestibulum ac lacus.";
+ assert_eq!(
+ rewrite_string(comment, &fmt, 13),
+ Some("Aenean metus.\n // Vestibulum ac\n // lacus.".to_string())
+ );
+
+ fmt.trim_end = false;
+ let comment = "Vestibulum ac lacus.";
+ assert_eq!(
+ rewrite_string(comment, &fmt, 13),
+ Some("Vestibulum \n // ac lacus.".to_string())
+ );
+
+ fmt.trim_end = true;
+ fmt.line_end = "\\";
+ let comment = "Vestibulum ac lacus.";
+ assert_eq!(
+ rewrite_string(comment, &fmt, 13),
+ Some("Vestibulum\\\n // ac lacus.".to_string())
+ );
+ }
+
+ #[test]
+ fn detect_urls() {
+ let string = "aaa http://example.org something";
+ let graphemes = UnicodeSegmentation::graphemes(&*string, false).collect::<Vec<&str>>();
+ assert_eq!(detect_url(&graphemes, 8), Some(21));
+
+ let string = "https://example.org something";
+ let graphemes = UnicodeSegmentation::graphemes(&*string, false).collect::<Vec<&str>>();
+ assert_eq!(detect_url(&graphemes, 0), Some(18));
+
+ let string = "aaa ftp://example.org something";
+ let graphemes = UnicodeSegmentation::graphemes(&*string, false).collect::<Vec<&str>>();
+ assert_eq!(detect_url(&graphemes, 8), Some(20));
+
+ let string = "aaa file://example.org something";
+ let graphemes = UnicodeSegmentation::graphemes(&*string, false).collect::<Vec<&str>>();
+ assert_eq!(detect_url(&graphemes, 8), Some(21));
+
+ let string = "aaa http not an url";
+ let graphemes = UnicodeSegmentation::graphemes(&*string, false).collect::<Vec<&str>>();
+ assert_eq!(detect_url(&graphemes, 6), None);
+
+ let string = "aaa file://example.org";
+ let graphemes = UnicodeSegmentation::graphemes(&*string, false).collect::<Vec<&str>>();
+ assert_eq!(detect_url(&graphemes, 8), Some(21));
+ }
}