Skip to content

Handle itemized blocks in comments #3083

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Oct 9, 2018
175 changes: 134 additions & 41 deletions src/comment.rs
Original file line number Diff line number Diff line change
@@ -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;
}

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;
} 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();
}
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(' ') {
414 changes: 367 additions & 47 deletions src/string.rs

Large diffs are not rendered by default.

46 changes: 46 additions & 0 deletions tests/source/itemized-blocks/no_wrap.rs
Original file line number Diff line number Diff line change
@@ -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() {}
11 changes: 11 additions & 0 deletions tests/source/itemized-blocks/rewrite_fail.rs
Original file line number Diff line number Diff line change
@@ -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() {}
22 changes: 22 additions & 0 deletions tests/source/itemized-blocks/urls.rs
Original file line number Diff line number Diff line change
@@ -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
54 changes: 54 additions & 0 deletions tests/source/itemized-blocks/wrap.rs
Original file line number Diff line number Diff line change
@@ -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() {}
4 changes: 2 additions & 2 deletions tests/target/comment5.rs
Original file line number Diff line number Diff line change
@@ -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() {}
4 changes: 2 additions & 2 deletions tests/target/enum.rs
Original file line number Diff line number Diff line change
@@ -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>>),
}
46 changes: 46 additions & 0 deletions tests/target/itemized-blocks/no_wrap.rs
Original file line number Diff line number Diff line change
@@ -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() {}
14 changes: 14 additions & 0 deletions tests/target/itemized-blocks/rewrite_fail.rs
Original file line number Diff line number Diff line change
@@ -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() {}
25 changes: 25 additions & 0 deletions tests/target/itemized-blocks/urls.rs
Original file line number Diff line number Diff line change
@@ -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
91 changes: 91 additions & 0 deletions tests/target/itemized-blocks/wrap.rs
Original file line number Diff line number Diff line change
@@ -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() {}
4 changes: 2 additions & 2 deletions tests/target/struct_lits.rs
Original file line number Diff line number Diff line change
@@ -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.
4 changes: 2 additions & 2 deletions tests/target/struct_lits_multiline.rs
Original file line number Diff line number Diff line change
@@ -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.