Merge pull request #3083 from scampi/itemized_blocks

Handle itemized blocks in comments
This commit is contained in:
Nick Cameron 2018-10-10 10:38:54 +12:00 committed by GitHub
commit 4895699e88
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 819 additions and 97 deletions

View file

@ -433,6 +433,58 @@ impl CodeBlockAttribute {
}
}
/// Block that is formatted as an item.
///
/// An item starts with either a star `*` or a dash `-`. Different level of indentation are
/// handled.
struct ItemizedBlock {
/// the number of whitespaces up to the item sigil
indent: usize,
/// the string that marks the start of an item
opener: String,
/// sequence of whitespaces to prefix new lines that are part of the item
line_start: String,
}
impl ItemizedBlock {
/// Returns true if the line is formatted as an item
fn is_itemized_line(line: &str) -> bool {
let trimmed = line.trim_left();
trimmed.starts_with("* ") || trimmed.starts_with("- ")
}
/// Creates a new ItemizedBlock described with the given line.
/// The `is_itemized_line` needs to be called first.
fn new(line: &str) -> ItemizedBlock {
let space_to_sigil = line.chars().take_while(|c| c.is_whitespace()).count();
let indent = space_to_sigil + 2;
ItemizedBlock {
indent,
opener: line[..indent].to_string(),
line_start: " ".repeat(indent),
}
}
/// Returns a `StringFormat` used for formatting the content of an item
fn create_string_format<'a>(&'a self, fmt: &'a StringFormat) -> StringFormat<'a> {
StringFormat {
opener: "",
closer: "",
line_start: "",
line_end: "",
shape: Shape::legacy(fmt.shape.width.saturating_sub(self.indent), Indent::empty()),
trim_end: true,
config: fmt.config,
}
}
/// Returns true if the line is part of the current itemized block
fn in_block(&self, line: &str) -> bool {
!ItemizedBlock::is_itemized_line(line)
&& self.indent <= line.chars().take_while(|c| c.is_whitespace()).count()
}
}
fn rewrite_comment_inner(
orig: &str,
block_style: bool,
@ -493,15 +545,17 @@ fn rewrite_comment_inner(
let mut code_block_buffer = String::with_capacity(128);
let mut is_prev_line_multi_line = false;
let mut code_block_attr = None;
let mut item_block_buffer = String::with_capacity(128);
let mut item_block: Option<ItemizedBlock> = None;
let comment_line_separator = format!("{}{}", indent_str, line_start);
let join_code_block_with_comment_line_separator = |s: &str| {
let join_block = |s: &str, sep: &str| {
let mut result = String::with_capacity(s.len() + 128);
let mut iter = s.lines().peekable();
while let Some(line) = iter.next() {
result.push_str(line);
result.push_str(match iter.peek() {
Some(next_line) if next_line.is_empty() => comment_line_separator.trim_right(),
Some(..) => &comment_line_separator,
Some(next_line) if next_line.is_empty() => sep.trim_right(),
Some(..) => &sep,
None => "",
});
}
@ -511,7 +565,26 @@ fn rewrite_comment_inner(
for (i, (line, has_leading_whitespace)) in lines.enumerate() {
let is_last = i == count_newlines(orig);
if let Some(ref attr) = code_block_attr {
if let Some(ref ib) = item_block {
if ib.in_block(&line) {
item_block_buffer.push_str(&line);
item_block_buffer.push('\n');
continue;
}
is_prev_line_multi_line = false;
fmt.shape = Shape::legacy(max_chars, fmt_indent);
let item_fmt = ib.create_string_format(&fmt);
result.push_str(&comment_line_separator);
result.push_str(&ib.opener);
match rewrite_string(&item_block_buffer.replace("\n", " "), &item_fmt) {
Some(s) => result.push_str(&join_block(
&s,
&format!("{}{}", &comment_line_separator, ib.line_start),
)),
None => result.push_str(&join_block(&item_block_buffer, &comment_line_separator)),
};
item_block_buffer.clear();
} else if let Some(ref attr) = code_block_attr {
if line.starts_with("```") {
let code_block = match attr {
CodeBlockAttribute::Ignore | CodeBlockAttribute::Text => {
@ -529,7 +602,7 @@ fn rewrite_comment_inner(
};
if !code_block.is_empty() {
result.push_str(&comment_line_separator);
result.push_str(&join_code_block_with_comment_line_separator(&code_block));
result.push_str(&join_block(&code_block, &comment_line_separator));
}
code_block_buffer.clear();
result.push_str(&comment_line_separator);
@ -538,46 +611,42 @@ fn rewrite_comment_inner(
} else {
code_block_buffer.push_str(&hide_sharp_behind_comment(line));
code_block_buffer.push('\n');
if is_last {
// There is a code block that is not properly enclosed by backticks.
// We will leave them untouched.
result.push_str(&comment_line_separator);
result.push_str(&join_code_block_with_comment_line_separator(
&trim_custom_comment_prefix(&code_block_buffer),
));
}
}
continue;
} else {
code_block_attr = if line.starts_with("```") {
Some(CodeBlockAttribute::new(&line[3..]))
} else {
None
};
}
if result == opener {
let force_leading_whitespace = opener == "/* " && count_newlines(orig) == 0;
if !has_leading_whitespace && !force_leading_whitespace && result.ends_with(' ') {
result.pop();
}
if line.is_empty() {
continue;
}
} else if is_prev_line_multi_line && !line.is_empty() {
result.push(' ')
} else if is_last && line.is_empty() {
// trailing blank lines are unwanted
if !closer.is_empty() {
result.push_str(&indent_str);
}
break;
} else {
result.push_str(&comment_line_separator);
if !has_leading_whitespace && result.ends_with(' ') {
result.pop();
}
code_block_attr = None;
item_block = None;
if line.starts_with("```") {
code_block_attr = Some(CodeBlockAttribute::new(&line[3..]))
} else if config.wrap_comments() && ItemizedBlock::is_itemized_line(&line) {
let ib = ItemizedBlock::new(&line);
item_block_buffer.push_str(&line[ib.indent..]);
item_block_buffer.push('\n');
item_block = Some(ib);
continue;
}
if result == opener {
let force_leading_whitespace = opener == "/* " && count_newlines(orig) == 0;
if !has_leading_whitespace && !force_leading_whitespace && result.ends_with(' ') {
result.pop();
}
if line.is_empty() {
continue;
}
} else if is_prev_line_multi_line && !line.is_empty() {
result.push(' ')
} else if is_last && line.is_empty() {
// trailing blank lines are unwanted
if !closer.is_empty() {
result.push_str(&indent_str);
}
break;
} else {
result.push_str(&comment_line_separator);
if !has_leading_whitespace && result.ends_with(' ') {
result.pop();
}
}
@ -631,6 +700,30 @@ fn rewrite_comment_inner(
is_prev_line_multi_line = false;
}
}
if !code_block_buffer.is_empty() {
// There is a code block that is not properly enclosed by backticks.
// We will leave them untouched.
result.push_str(&comment_line_separator);
result.push_str(&join_block(
&trim_custom_comment_prefix(&code_block_buffer),
&comment_line_separator,
));
}
if !item_block_buffer.is_empty() {
// the last few lines are part of an itemized block
let ib = item_block.unwrap();
fmt.shape = Shape::legacy(max_chars, fmt_indent);
let item_fmt = ib.create_string_format(&fmt);
result.push_str(&comment_line_separator);
result.push_str(&ib.opener);
match rewrite_string(&item_block_buffer.replace("\n", " "), &item_fmt) {
Some(s) => result.push_str(&join_block(
&s,
&format!("{}{}", &comment_line_separator, ib.line_start),
)),
None => result.push_str(&join_block(&item_block_buffer, &comment_line_separator)),
};
}
result.push_str(closer);
if result.ends_with(opener) && opener.ends_with(' ') {

View file

@ -64,7 +64,7 @@ impl<'a> StringFormat<'a> {
/// Like max_chars_with_indent but the indentation is not substracted.
/// This allows to fit more graphemes from the string on a line when
/// SnippetState::Overflow.
/// SnippetState::EndWithLineFeed.
fn max_chars_without_indent(&self) -> Option<usize> {
Some(self.config.max_width().checked_sub(self.line_end.len())?)
}
@ -73,7 +73,8 @@ impl<'a> StringFormat<'a> {
pub fn rewrite_string<'a>(orig: &str, fmt: &StringFormat<'a>) -> Option<String> {
let max_chars_with_indent = fmt.max_chars_with_indent()?;
let max_chars_without_indent = fmt.max_chars_without_indent()?;
let indent = fmt.shape.indent.to_string_with_newline(fmt.config);
let indent_with_newline = fmt.shape.indent.to_string_with_newline(fmt.config);
let indent_without_newline = fmt.shape.indent.to_string(fmt.config);
// Strip line breaks.
// With this regex applied, all remaining whitespaces are significant
@ -95,26 +96,55 @@ pub fn rewrite_string<'a>(orig: &str, fmt: &StringFormat<'a>) -> Option<String>
// Snip a line at a time from `stripped_str` until it is used up. Push the snippet
// onto result.
let mut cur_max_chars = max_chars_with_indent;
let is_bareline_ok = fmt.line_start.is_empty() || is_whitespace(fmt.line_start);
loop {
// All the input starting at cur_start fits on the current line
if graphemes.len() - cur_start <= cur_max_chars {
result.push_str(&graphemes[cur_start..].join(""));
for (i, grapheme) in graphemes[cur_start..].iter().enumerate() {
if is_line_feed(grapheme) {
// take care of blank lines
result = trim_right_but_line_feed(fmt.trim_end, result);
result.push_str("\n");
if !is_bareline_ok && cur_start + i + 1 < graphemes.len() {
result.push_str(&indent_without_newline);
result.push_str(fmt.line_start);
}
} else {
result.push_str(grapheme);
}
}
result = trim_right_but_line_feed(fmt.trim_end, result);
break;
}
// 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);
result.push_str(&indent_with_newline);
result.push_str(fmt.line_start);
cur_max_chars = max_chars_with_indent;
cur_start += len;
}
SnippetState::Overflow(line, len) => {
SnippetState::EndWithLineFeed(line, len) => {
if line == "\n" && fmt.trim_end {
result = result.trim_right().to_string();
}
result.push_str(&line);
cur_max_chars = max_chars_without_indent;
if is_bareline_ok {
// the next line can benefit from the full width
cur_max_chars = max_chars_without_indent;
} else {
result.push_str(&indent_without_newline);
result.push_str(fmt.line_start);
cur_max_chars = max_chars_with_indent;
}
cur_start += len;
}
SnippetState::EndOfInput(line) => {
@ -128,6 +158,43 @@ pub fn rewrite_string<'a>(orig: &str, fmt: &StringFormat<'a>) -> Option<String>
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].join("");
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';
if trim_end && result.ends_with(whitespace_except_line_feed) {
result
.trim_right_matches(whitespace_except_line_feed)
.to_string()
} else {
result
}
}
/// Result of breaking a string so it fits in a line and the state it ended in.
/// The state informs about what to do with the snippet and how to continue the breaking process.
#[derive(Debug, PartialEq)]
@ -136,36 +203,46 @@ enum SnippetState {
EndOfInput(String),
/// The input could be broken and the returned snippet should be ended with a
/// `[StringFormat::line_end]`. The next snippet needs to be indented.
///
/// The returned string is the line to print out and the number is the length that got read in
/// the text being rewritten. That length may be greater than the returned string if trailing
/// whitespaces got trimmed.
LineEnd(String, usize),
/// The input could be broken but the returned snippet should not be ended with a
/// `[StringFormat::line_end]` because the whitespace is significant. Therefore, the next
/// snippet should not be indented.
Overflow(String, usize),
/// The input could be broken but a newline is present that cannot be trimmed. The next snippet
/// to be rewritten *could* use more width than what is specified by the given shape. For
/// example with a multiline string, the next snippet does not need to be indented, allowing
/// more characters to be fit within a line.
///
/// The returned string is the line to print out and the number is the length that got read in
/// the text being rewritten.
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]` and
// check if there is a line feed, in which case whitespaces needs to be kept.
let mut index_minus_ws = index;
for (i, grapheme) in input[0..=index].iter().enumerate().rev() {
if !is_whitespace(grapheme) {
index_minus_ws = i;
break;
}
}
// Take in any whitespaces to the left/right of `input[index]` while
// preserving line feeds
let index_minus_ws = input[0..=index]
.iter()
.rposition(|grapheme| not_whitespace_except_line_feed(grapheme))
.unwrap_or(index);
// Take into account newlines occuring in input[0..=index], i.e., the possible next new
// line. If there is one, then text after it could be rewritten in a way that the available
// space is fully used.
for (i, grapheme) in input[0..=index].iter().enumerate() {
if is_line_feed(grapheme) {
if i < index_minus_ws || !trim_end {
return SnippetState::Overflow(input[0..=i].join("").to_string(), i + 1);
if i <= index_minus_ws {
let mut line = input[0..i].join("");
if trim_end {
line = line.trim_right().to_string();
}
return SnippetState::EndWithLineFeed(format!("{}\n", line), i + 1);
}
break;
}
@ -174,11 +251,11 @@ fn break_string(max_chars: usize, trim_end: bool, input: &[&str]) -> SnippetStat
let mut index_plus_ws = index;
for (i, grapheme) in input[index + 1..].iter().enumerate() {
if !trim_end && is_line_feed(grapheme) {
return SnippetState::Overflow(
return SnippetState::EndWithLineFeed(
input[0..=index + 1 + i].join("").to_string(),
index + 2 + i,
);
} else if !is_whitespace(grapheme) {
} else if not_whitespace_except_line_feed(grapheme) {
index_plus_ws = index + i;
break;
}
@ -198,6 +275,35 @@ fn break_string(max_chars: usize, trim_end: bool, input: &[&str]) -> SnippetStat
};
// 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].join("").to_string(),
index_plus_ws + 1,
)
} else {
return SnippetState::LineEnd(
input[..=index_plus_ws].join("").to_string(),
index_plus_ws + 1,
);
};
}
match input[0..max_chars]
.iter()
.rposition(|grapheme| is_whitespace(grapheme))
@ -243,7 +349,7 @@ fn is_punctuation(grapheme: &str) -> bool {
#[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;
@ -260,11 +366,11 @@ mod 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)
);
}
@ -274,7 +380,7 @@ mod 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(), 15)
);
}
@ -284,11 +390,11 @@ mod test {
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)
);
}
@ -298,7 +404,7 @@ mod test {
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())
);
}
@ -308,21 +414,21 @@ mod test {
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[..]),
SnippetState::Overflow("Neque in sem. \n".to_string(), 20)
break_string(15, false, "", &graphemes[..]),
SnippetState::EndWithLineFeed("Neque in sem. \n".to_string(), 20)
);
assert_eq!(
break_string(25, false, &graphemes[..]),
SnippetState::Overflow("Neque in sem. \n".to_string(), 20)
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)
);
}
@ -331,11 +437,11 @@ mod test {
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)
);
}
@ -346,12 +452,12 @@ mod test {
let graphemes = UnicodeSegmentation::graphemes(&*string, false).collect::<Vec<&str>>();
assert_eq!(
break_string(25, false, &graphemes[..]),
SnippetState::Overflow("Nulla\n".to_string(), 6)
break_string(25, false, "", &graphemes[..]),
SnippetState::EndWithLineFeed("Nulla\n".to_string(), 6)
);
assert_eq!(
break_string(25, true, &graphemes[..]),
SnippetState::Overflow("Nulla\n".to_string(), 6)
break_string(25, true, "", &graphemes[..]),
SnippetState::EndWithLineFeed("Nulla\n".to_string(), 6)
);
let mut config: Config = Default::default();
@ -363,4 +469,218 @@ mod test {
Some("\"Nulla\nconsequat erat at massa. \\\n Vivamus id mi.\"".to_string())
);
}
#[test]
fn last_line_fit_with_trailing_whitespaces() {
let string = "Vivamus id mi. ";
let config: Config = Default::default();
let mut fmt = StringFormat::new(Shape::legacy(25, Indent::empty()), &config);
fmt.trim_end = true;
let rewritten_string = rewrite_string(string, &fmt);
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);
assert_eq!(rewritten_string, Some("\"Vivamus id mi. \"".to_string()));
}
#[test]
fn last_line_fit_with_newline() {
let string = "Vivamus id mi.\nVivamus id mi.";
let config: Config = Default::default();
let fmt = StringFormat {
opener: "",
closer: "",
line_start: "// ",
line_end: "",
shape: Shape::legacy(100, Indent::from_width(&config, 4)),
trim_end: true,
config: &config,
};
let rewritten_string = rewrite_string(string, &fmt);
assert_eq!(
rewritten_string,
Some("Vivamus id mi.\n // Vivamus id mi.".to_string())
);
}
#[test]
fn overflow_in_non_string_content() {
let comment = "Aenean metus.\nVestibulum ac lacus. Vivamus porttitor";
let config: Config = Default::default();
let fmt = StringFormat {
opener: "",
closer: "",
line_start: "// ",
line_end: "",
shape: Shape::legacy(30, Indent::from_width(&config, 8)),
trim_end: true,
config: &config,
};
assert_eq!(
rewrite_string(comment, &fmt),
Some(
"Aenean metus.\n // Vestibulum ac lacus. Vivamus\n // porttitor"
.to_string()
)
);
}
#[test]
fn overflow_in_non_string_content_with_line_end() {
let comment = "Aenean metus.\nVestibulum ac lacus. Vivamus porttitor";
let config: Config = Default::default();
let fmt = StringFormat {
opener: "",
closer: "",
line_start: "// ",
line_end: "@",
shape: Shape::legacy(30, Indent::from_width(&config, 8)),
trim_end: true,
config: &config,
};
assert_eq!(
rewrite_string(comment, &fmt),
Some(
"Aenean metus.\n // Vestibulum ac lacus. Vivamus@\n // porttitor"
.to_string()
)
);
}
#[test]
fn blank_line_with_non_empty_line_start() {
let config: Config = Default::default();
let mut fmt = StringFormat {
opener: "",
closer: "",
line_start: "// ",
line_end: "",
shape: Shape::legacy(30, Indent::from_width(&config, 4)),
trim_end: true,
config: &config,
};
let comment = "Aenean metus. Vestibulum\n\nac lacus. Vivamus porttitor";
assert_eq!(
rewrite_string(comment, &fmt),
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),
Some(
r#"Aenean
//
// metus. Vestibulum
// ac lacus. Vivamus
// porttitor"#
.to_string()
)
);
}
#[test]
fn retain_blank_lines() {
let config: Config = Default::default();
let fmt = StringFormat {
opener: "",
closer: "",
line_start: "// ",
line_end: "",
shape: Shape::legacy(20, Indent::from_width(&config, 4)),
trim_end: true,
config: &config,
};
let comment = "Aenean\n\nmetus. Vestibulum ac lacus.\n\n";
assert_eq!(
rewrite_string(comment, &fmt),
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),
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),
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),
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),
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),
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));
}
}

View file

@ -0,0 +1,46 @@
// rustfmt-normalize_comments: true
//! This is a list:
//! * Outer
//! * Outer
//! * Inner
//! * Inner with lots of text so that it could be reformatted something something something lots of text so that it could be reformatted something something something
//!
//! This example shows how to configure fern to output really nicely colored logs
//! - when the log level is error, the whole line is red
//! - when the log level is warn, the whole line is yellow
//! - when the log level is info, the level name is green and the rest of the line is white
//! - when the log level is debug, the whole line is white
//! - when the log level is trace, the whole line is gray ("bright black")
/// All the parameters ***except for `from_theater`*** should be inserted as sent by the remote
/// theater, ie. as passed to [`Theater::send`] on the remote actor:
/// * `from` is the sending (remote) [`ActorId`], as reported by the remote theater by theater-specific means
/// * `to` is the receiving (local) [`ActorId`], as requested by the remote theater
/// * `tag` is a tag that identifies the message type
/// * `msg` is the (serialized) message
/// All the parameters ***except for `from_theater`*** should be inserted as sent by the remote
/// theater, ie. as passed to [`Theater::send`] on the remote actor
fn func1() {}
/// All the parameters ***except for `from_theater`*** should be inserted as sent by the remote
/// theater, ie. as passed to [`Theater::send`] on the remote actor:
/// * `from` is the sending (remote) [`ActorId`], as reported by the remote theater by theater-specific means
/// * `to` is the receiving (local) [`ActorId`], as requested by the remote theater
/// * `tag` is a tag that identifies the message type
/// * `msg` is the (serialized) message
/// ```
/// let x = 42;
/// ```
fn func2() {}
/// Look:
///
/// ```
/// let x = 42;
/// ```
/// * `from` is the sending (remote) [`ActorId`], as reported by the remote theater by theater-specific means
/// * `to` is the receiving (local) [`ActorId`], as requested by the remote theater
/// * `tag` is a tag that identifies the message type
/// * `msg` is the (serialized) message
fn func3() {}

View file

@ -0,0 +1,11 @@
// rustfmt-wrap_comments: true
// rustfmt-max_width: 50
// This example shows how to configure fern to output really nicely colored logs
// - aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
// - aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
// - aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
// - when the log level is info, the level name is green and the rest of the line is white
// - aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
// - aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
fn func1() {}

View file

@ -0,0 +1,22 @@
// rustfmt-wrap_comments: true
// rustfmt-max_width: 79
//! CMSIS: Cortex Microcontroller Software Interface Standard
//!
//! The version 5 of the standard can be found at:
//!
//! http://arm-software.github.io/CMSIS_5/Core/html/index.html
//!
//! The API reference of the standard can be found at:
//!
//! - example -- http://example.org -- something something something something something something
//! - something something something something something something more -- http://example.org
//! - http://example.org/something/something/something/something/something/something and the rest
//! - Core function access -- http://arm-software.github.io/CMSIS_5/Core/html/group__Core__Register__gr.html
//! - Intrinsic functions for CPU instructions -- http://arm-software.github.io/CMSIS_5/Core/html/group__intrinsic__CPU__gr.html
//! - Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Vestibulum sem lacus, commodo vitae.
//!
//! The reference C implementation used as the base of this Rust port can be
//! found at
//!
//! https://github.com/ARM-software/CMSIS_5/blob/5.3.0/CMSIS/Core/Include/cmsis_gcc.h

View file

@ -0,0 +1,54 @@
// rustfmt-wrap_comments: true
// rustfmt-max_width: 50
//! This is a list:
//! * Outer
//! * Outer
//! * Inner
//! * Inner with lots of text so that it could be reformatted something something something lots of text so that it could be reformatted something something something
//!
//! This example shows how to configure fern to output really nicely colored logs
//! - when the log level is error, the whole line is red
//! - when the log level is warn, the whole line is yellow
//! - when the log level is info, the level name is green and the rest of the line is white
//! - when the log level is debug, the whole line is white
//! - when the log level is trace, the whole line is gray ("bright black")
// This example shows how to configure fern to output really nicely colored logs
// - when the log level is error, the whole line is red
// - when the log level is warn, the whole line is yellow
// - when the log level is info, the level name is green and the rest of the line is white
// - when the log level is debug, the whole line is white
// - when the log level is trace, the whole line is gray ("bright black")
/// All the parameters ***except for `from_theater`*** should be inserted as sent by the remote
/// theater, ie. as passed to [`Theater::send`] on the remote actor:
/// * `from` is the sending (remote) [`ActorId`], as reported by the remote theater by theater-specific means
/// * `to` is the receiving (local) [`ActorId`], as requested by the remote theater
/// * `tag` is a tag that identifies the message type
/// * `msg` is the (serialized) message
/// All the parameters ***except for `from_theater`*** should be inserted as sent by the remote
/// theater, ie. as passed to [`Theater::send`] on the remote actor
fn func1() {}
/// All the parameters ***except for `from_theater`*** should be inserted as sent by the remote
/// theater, ie. as passed to [`Theater::send`] on the remote actor:
/// * `from` is the sending (remote) [`ActorId`], as reported by the remote theater by theater-specific means
/// * `to` is the receiving (local) [`ActorId`], as requested by the remote theater
/// * `tag` is a tag that identifies the message type
/// * `msg` is the (serialized) message
/// ```
/// let x = 42;
/// ```
fn func2() {}
/// Look:
///
/// ```
/// let x = 42;
/// ```
/// * `from` is the sending (remote) [`ActorId`], as reported by the remote theater by theater-specific means
/// * `to` is the receiving (local) [`ActorId`], as requested by the remote theater
/// * `tag` is a tag that identifies the message type
/// * `msg` is the (serialized) message
fn func3() {}

View file

@ -2,8 +2,8 @@
// rustfmt-wrap_comments: true
//@ special comment
//@ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec adiam
//@ lectus. Sed sit amet ipsum mauris. Maecenas congue ligula ac quam
//@ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec adiam lectus.
//@ Sed sit amet ipsum mauris. Maecenas congue ligula ac quam
//@
//@ foo
fn test() {}

View file

@ -145,8 +145,8 @@ pub enum Bencoding<'i> {
Int(i64),
List(Vec<Bencoding<'i>>),
/// A bencoded dict value. The first element the slice of bytes in the
/// source that the dict is composed of. The second is the dict,
/// decoded into an ordered map.
/// source that the dict is composed of. The second is the dict, decoded
/// into an ordered map.
// TODO make Dict "structlike" AKA name the two values.
Dict(&'i [u8], BTreeMap<&'i [u8], Bencoding<'i>>),
}

View file

@ -0,0 +1,46 @@
// rustfmt-normalize_comments: true
//! This is a list:
//! * Outer
//! * Outer
//! * Inner
//! * Inner with lots of text so that it could be reformatted something something something lots of text so that it could be reformatted something something something
//!
//! This example shows how to configure fern to output really nicely colored logs
//! - when the log level is error, the whole line is red
//! - when the log level is warn, the whole line is yellow
//! - when the log level is info, the level name is green and the rest of the line is white
//! - when the log level is debug, the whole line is white
//! - when the log level is trace, the whole line is gray ("bright black")
/// All the parameters ***except for `from_theater`*** should be inserted as sent by the remote
/// theater, ie. as passed to [`Theater::send`] on the remote actor:
/// * `from` is the sending (remote) [`ActorId`], as reported by the remote theater by theater-specific means
/// * `to` is the receiving (local) [`ActorId`], as requested by the remote theater
/// * `tag` is a tag that identifies the message type
/// * `msg` is the (serialized) message
/// All the parameters ***except for `from_theater`*** should be inserted as sent by the remote
/// theater, ie. as passed to [`Theater::send`] on the remote actor
fn func1() {}
/// All the parameters ***except for `from_theater`*** should be inserted as sent by the remote
/// theater, ie. as passed to [`Theater::send`] on the remote actor:
/// * `from` is the sending (remote) [`ActorId`], as reported by the remote theater by theater-specific means
/// * `to` is the receiving (local) [`ActorId`], as requested by the remote theater
/// * `tag` is a tag that identifies the message type
/// * `msg` is the (serialized) message
/// ```
/// let x = 42;
/// ```
fn func2() {}
/// Look:
///
/// ```
/// let x = 42;
/// ```
/// * `from` is the sending (remote) [`ActorId`], as reported by the remote theater by theater-specific means
/// * `to` is the receiving (local) [`ActorId`], as requested by the remote theater
/// * `tag` is a tag that identifies the message type
/// * `msg` is the (serialized) message
fn func3() {}

View file

@ -0,0 +1,14 @@
// rustfmt-wrap_comments: true
// rustfmt-max_width: 50
// This example shows how to configure fern to
// output really nicely colored logs
// - aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
// - aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
// - aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
// - when the log level is info, the level
// name is green and the rest of the line is
// white
// - aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
// - aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
fn func1() {}

View file

@ -0,0 +1,25 @@
// rustfmt-wrap_comments: true
// rustfmt-max_width: 79
//! CMSIS: Cortex Microcontroller Software Interface Standard
//!
//! The version 5 of the standard can be found at:
//!
//! http://arm-software.github.io/CMSIS_5/Core/html/index.html
//!
//! The API reference of the standard can be found at:
//!
//! - example -- http://example.org -- something something something something
//! something something
//! - something something something something something something more -- http://example.org
//! - http://example.org/something/something/something/something/something/something
//! and the rest
//! - Core function access -- http://arm-software.github.io/CMSIS_5/Core/html/group__Core__Register__gr.html
//! - Intrinsic functions for CPU instructions -- http://arm-software.github.io/CMSIS_5/Core/html/group__intrinsic__CPU__gr.html
//! - Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Vestibulum sem
//! lacus, commodo vitae.
//!
//! The reference C implementation used as the base of this Rust port can be
//! found at
//!
//! https://github.com/ARM-software/CMSIS_5/blob/5.3.0/CMSIS/Core/Include/cmsis_gcc.h

View file

@ -0,0 +1,91 @@
// rustfmt-wrap_comments: true
// rustfmt-max_width: 50
//! This is a list:
//! * Outer
//! * Outer
//! * Inner
//! * Inner with lots of text so that it could
//! be reformatted something something
//! something lots of text so that it could be
//! reformatted something something something
//!
//! This example shows how to configure fern to
//! output really nicely colored logs
//! - when the log level is error, the whole line
//! is red
//! - when the log level is warn, the whole line
//! is yellow
//! - when the log level is info, the level name
//! is green and the rest of the line is white
//! - when the log level is debug, the whole line
//! is white
//! - when the log level is trace, the whole line
//! is gray ("bright black")
// This example shows how to configure fern to
// output really nicely colored logs
// - when the log level is error, the whole line
// is red
// - when the log level is warn, the whole line
// is yellow
// - when the log level is info, the level
// name is green and the rest of the line is
// white
// - when the log level is debug, the whole line
// is white
// - when the log level is trace, the whole line
// is gray ("bright black")
/// All the parameters ***except for
/// `from_theater`*** should be inserted as sent
/// by the remote theater, ie. as passed to
/// [`Theater::send`] on the remote
/// actor:
/// * `from` is the sending (remote) [`ActorId`],
/// as reported by the remote theater by
/// theater-specific means
/// * `to` is the receiving (local) [`ActorId`],
/// as requested by the remote theater
/// * `tag` is a tag that identifies the message
/// type
/// * `msg` is the (serialized) message
/// All the parameters ***except for
/// `from_theater`*** should be inserted as sent
/// by the remote theater, ie. as passed to
/// [`Theater::send`] on the remote
/// actor
fn func1() {}
/// All the parameters ***except for
/// `from_theater`*** should be inserted as sent
/// by the remote theater, ie. as passed to
/// [`Theater::send`] on the remote
/// actor:
/// * `from` is the sending (remote) [`ActorId`],
/// as reported by the remote theater by
/// theater-specific means
/// * `to` is the receiving (local) [`ActorId`],
/// as requested by the remote theater
/// * `tag` is a tag that identifies the message
/// type
/// * `msg` is the (serialized) message
/// ```
/// let x = 42;
/// ```
fn func2() {}
/// Look:
///
/// ```
/// let x = 42;
/// ```
/// * `from` is the sending (remote) [`ActorId`],
/// as reported by the remote theater by
/// theater-specific means
/// * `to` is the receiving (local) [`ActorId`],
/// as requested by the remote theater
/// * `tag` is a tag that identifies the message
/// type
/// * `msg` is the (serialized) message
fn func3() {}

View file

@ -40,8 +40,8 @@ fn main() {
A {
// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec a diam lectus. Sed sit
// amet ipsum mauris. Maecenas congue ligula ac quam viverra nec consectetur ante
// hendrerit. Donec et mollis dolor.
// amet ipsum mauris. Maecenas congue ligula ac quam viverra nec consectetur ante hendrerit.
// Donec et mollis dolor.
first: item(),
// Praesent et diam eget libero egestas mattis sit amet vitae augue.
// Nam tincidunt congue enim, ut porta lorem lacinia consectetur.

View file

@ -50,8 +50,8 @@ fn main() {
A {
// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec a diam lectus. Sed sit
// amet ipsum mauris. Maecenas congue ligula ac quam viverra nec consectetur ante
// hendrerit. Donec et mollis dolor.
// amet ipsum mauris. Maecenas congue ligula ac quam viverra nec consectetur ante hendrerit.
// Donec et mollis dolor.
first: item(),
// Praesent et diam eget libero egestas mattis sit amet vitae augue.
// Nam tincidunt congue enim, ut porta lorem lacinia consectetur.