handle itemized items inside comments
This commit is contained in:
parent
8b3788d875
commit
c0b7222e2d
5 changed files with 372 additions and 42 deletions
177
src/comment.rs
177
src/comment.rs
|
@ -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(' ') {
|
||||
|
|
46
tests/source/itemized-blocks/no_wrap.rs
Normal file
46
tests/source/itemized-blocks/no_wrap.rs
Normal 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() {}
|
54
tests/source/itemized-blocks/wrap.rs
Normal file
54
tests/source/itemized-blocks/wrap.rs
Normal 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() {}
|
46
tests/target/itemized-blocks/no_wrap.rs
Normal file
46
tests/target/itemized-blocks/no_wrap.rs
Normal 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() {}
|
91
tests/target/itemized-blocks/wrap.rs
Normal file
91
tests/target/itemized-blocks/wrap.rs
Normal 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() {}
|
Loading…
Reference in a new issue