Skip to content

Commit 1e78a2b

Browse files
ytmimicalebcartwright
authored andcommitted
Leverage itemized blocks to support formatting markdown block quotes
Fixes 5157 Doc comments support markdown, but rustfmt didn't previously assign any semantic value to leading '> ' in comments. This lead to poor formatting when using ``wrap_comments=true``. Now, rustfmt treats block quotes as itemized blocks, which greatly improves how block quotes are formatted when ``wrap_comments=true``.
1 parent b05b313 commit 1e78a2b

7 files changed

+87
-7
lines changed

src/comment.rs

Lines changed: 39 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -432,36 +432,49 @@ impl CodeBlockAttribute {
432432

433433
/// Block that is formatted as an item.
434434
///
435-
/// An item starts with either a star `*` or a dash `-`. Different level of indentation are
436-
/// handled by shrinking the shape accordingly.
435+
/// An item starts with either a star `*` a dash `-` or a greater-than `>`.
436+
/// Different level of indentation are handled by shrinking the shape accordingly.
437437
struct ItemizedBlock {
438438
/// the lines that are identified as part of an itemized block
439439
lines: Vec<String>,
440-
/// the number of whitespaces up to the item sigil
440+
/// the number of characters (typically whitespaces) up to the item sigil
441441
indent: usize,
442442
/// the string that marks the start of an item
443443
opener: String,
444-
/// sequence of whitespaces to prefix new lines that are part of the item
444+
/// sequence of characters (typically whitespaces) to prefix new lines that are part of the item
445445
line_start: String,
446446
}
447447

448448
impl ItemizedBlock {
449449
/// Returns `true` if the line is formatted as an item
450450
fn is_itemized_line(line: &str) -> bool {
451451
let trimmed = line.trim_start();
452-
trimmed.starts_with("* ") || trimmed.starts_with("- ")
452+
trimmed.starts_with("* ") || trimmed.starts_with("- ") || trimmed.starts_with("> ")
453453
}
454454

455455
/// Creates a new ItemizedBlock described with the given line.
456456
/// The `is_itemized_line` needs to be called first.
457457
fn new(line: &str) -> ItemizedBlock {
458458
let space_to_sigil = line.chars().take_while(|c| c.is_whitespace()).count();
459-
let indent = space_to_sigil + 2;
459+
// +2 = '* ', which will add the appropriate amount of whitespace to keep itemized
460+
// content formatted correctly.
461+
let mut indent = space_to_sigil + 2;
462+
let mut line_start = " ".repeat(indent);
463+
464+
// Markdown blockquote start with a "> "
465+
if line.trim_start().starts_with(">") {
466+
// remove the original +2 indent because there might be multiple nested block quotes
467+
// and it's easier to reason about the final indent by just taking the length
468+
// of th new line_start. We update the indent because it effects the max width
469+
// of each formatted line.
470+
line_start = itemized_block_quote_start(line, line_start, 2);
471+
indent = line_start.len();
472+
}
460473
ItemizedBlock {
461474
lines: vec![line[indent..].to_string()],
462475
indent,
463476
opener: line[..indent].to_string(),
464-
line_start: " ".repeat(indent),
477+
line_start,
465478
}
466479
}
467480

@@ -504,6 +517,25 @@ impl ItemizedBlock {
504517
}
505518
}
506519

520+
/// Determine the line_start when formatting markdown block quotes.
521+
/// The original line_start likely contains indentation (whitespaces), which we'd like to
522+
/// replace with '> ' characters.
523+
fn itemized_block_quote_start(line: &str, mut line_start: String, remove_indent: usize) -> String {
524+
let quote_level = line
525+
.chars()
526+
.take_while(|c| !c.is_alphanumeric())
527+
.fold(0, |acc, c| if c == '>' { acc + 1 } else { acc });
528+
529+
for _ in 0..remove_indent {
530+
line_start.pop();
531+
}
532+
533+
for _ in 0..quote_level {
534+
line_start.push_str("> ")
535+
}
536+
line_start
537+
}
538+
507539
struct CommentRewrite<'a> {
508540
result: String,
509541
code_block_buffer: String,
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
// rustfmt-wrap_comments: true
2+
3+
/// > For each sample received, the middleware internally maintains a sample_state relative to each DataReader. The sample_state can either be READ or NOT_READ.
4+
fn block_quote() {}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// rustfmt-wrap_comments: true
2+
3+
/// > For each sample received, the middleware internally maintains a sample_state relative to each DataReader. The sample_state can either be READ or NOT_READ.
4+
///
5+
/// > > For each sample received, the middleware internally maintains a sample_state relative to each DataReader. The sample_state can either be READ or NOT_READ.
6+
///
7+
/// > > > For each sample received, the middleware internally maintains a sample_state relative to each DataReader. The sample_state can either be READ or NOT_READ.
8+
///
9+
/// > > > > > > > > For each sample received, the middleware internally maintains a sample_state relative to each DataReader. The sample_state can either be READ or NOT_READ.
10+
fn block_quote() {}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
// rustfmt-wrap_comments: true
2+
3+
/// > For each sample received, the middleware internally maintains a sample_state relative to each DataReader. The sample_state can either be READ or NOT_READ.
4+
fn block_quote() {}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// rustfmt-wrap_comments: true
2+
3+
/// > For each sample received, the middleware internally maintains a
4+
/// > sample_state relative to each DataReader. The sample_state can
5+
/// > either be READ or NOT_READ.
6+
fn block_quote() {}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// rustfmt-wrap_comments: true
2+
3+
/// > For each sample received, the middleware internally maintains a
4+
/// > sample_state relative to each DataReader. The sample_state can either be
5+
/// > READ or NOT_READ.
6+
///
7+
/// > > For each sample received, the middleware internally maintains a
8+
/// > > sample_state relative to each DataReader. The sample_state can either be
9+
/// > > READ or NOT_READ.
10+
///
11+
/// > > > For each sample received, the middleware internally maintains a
12+
/// > > > sample_state relative to each DataReader. The sample_state can either
13+
/// > > > be READ or NOT_READ.
14+
///
15+
/// > > > > > > > > For each sample received, the middleware internally
16+
/// > > > > > > > > maintains a sample_state relative to each DataReader. The
17+
/// > > > > > > > > sample_state can either be READ or NOT_READ.
18+
fn block_quote() {}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// rustfmt-wrap_comments: true
2+
3+
/// > For each sample received, the middleware internally maintains a
4+
/// > sample_state relative to each DataReader. The sample_state can either be
5+
/// > READ or NOT_READ.
6+
fn block_quote() {}

0 commit comments

Comments
 (0)