Skip to content

Commit 809e06e

Browse files
authored
Merge pull request #2102 from topecongiro/soft-wrapping-comments
Soft wrapping for comments
2 parents be95966 + 471e911 commit 809e06e

File tree

5 files changed

+93
-13
lines changed

5 files changed

+93
-13
lines changed

src/comment.rs

Lines changed: 58 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -287,12 +287,13 @@ fn rewrite_comment_inner(
287287
.checked_sub(closer.len() + opener.len())
288288
.unwrap_or(1);
289289
let indent_str = shape.indent.to_string(config);
290-
let fmt = StringFormat {
290+
let fmt_indent = shape.indent + (opener.len() - line_start.len());
291+
let mut fmt = StringFormat {
291292
opener: "",
292293
closer: "",
293294
line_start: line_start,
294295
line_end: "",
295-
shape: Shape::legacy(max_chars, shape.indent + (opener.len() - line_start.len())),
296+
shape: Shape::legacy(max_chars, fmt_indent),
296297
trim_end: true,
297298
config: config,
298299
};
@@ -317,26 +318,69 @@ fn rewrite_comment_inner(
317318
});
318319

319320
let mut result = opener.to_owned();
321+
let mut is_prev_line_multi_line = false;
322+
let comment_line_separator = format!("\n{}{}", indent_str, line_start);
320323
for line in lines {
321324
if result == opener {
322325
if line.is_empty() {
323326
continue;
324327
}
325328
} else {
326-
result.push('\n');
327-
result.push_str(&indent_str);
328-
result.push_str(line_start);
329+
if is_prev_line_multi_line && !line.is_empty() {
330+
result.push(' ')
331+
} else {
332+
result.push_str(&comment_line_separator);
333+
}
329334
}
330335

331-
if config.wrap_comments() && line.len() > max_chars {
332-
let rewrite = rewrite_string(line, &fmt).unwrap_or_else(|| line.to_owned());
333-
result.push_str(&rewrite);
336+
if config.wrap_comments() && line.len() > fmt.shape.width && !has_url(line) {
337+
match rewrite_string(line, &fmt, Some(max_chars)) {
338+
Some(ref s) => {
339+
is_prev_line_multi_line = s.contains('\n');
340+
result.push_str(s);
341+
}
342+
None if is_prev_line_multi_line => {
343+
// We failed to put the current `line` next to the previous `line`.
344+
// Remove the trailing space, then start rewrite on the next line.
345+
result.pop();
346+
result.push_str(&comment_line_separator);
347+
fmt.shape = Shape::legacy(max_chars, fmt_indent);
348+
match rewrite_string(line, &fmt, Some(max_chars)) {
349+
Some(ref s) => {
350+
is_prev_line_multi_line = s.contains('\n');
351+
result.push_str(s);
352+
}
353+
None => {
354+
is_prev_line_multi_line = false;
355+
result.push_str(line);
356+
}
357+
}
358+
}
359+
None => {
360+
is_prev_line_multi_line = false;
361+
result.push_str(line);
362+
}
363+
}
364+
365+
fmt.shape = if is_prev_line_multi_line {
366+
// 1 = " "
367+
let offset = 1 + last_line_width(&result) - line_start.len();
368+
Shape {
369+
width: max_chars.checked_sub(offset).unwrap_or(0),
370+
indent: fmt_indent,
371+
offset: fmt.shape.offset + offset,
372+
}
373+
} else {
374+
Shape::legacy(max_chars, fmt_indent)
375+
};
334376
} else {
335377
if line.is_empty() && result.ends_with(' ') {
336378
// Remove space if this is an empty comment or a doc comment.
337379
result.pop();
338380
}
339381
result.push_str(line);
382+
fmt.shape = Shape::legacy(max_chars, fmt_indent);
383+
is_prev_line_multi_line = false;
340384
}
341385
}
342386

@@ -349,6 +393,12 @@ fn rewrite_comment_inner(
349393
Some(result)
350394
}
351395

396+
/// Returns true if the given string MAY include URLs or alike.
397+
fn has_url(s: &str) -> bool {
398+
// This function may return false positive, but should get its job done in most cases.
399+
s.contains("https://") || s.contains("http://") || s.contains("ftp://") || s.contains("file://")
400+
}
401+
352402
/// Given the span, rewrite the missing comment inside it if available.
353403
/// Note that the given span must only include comments (or leading/trailing whitespaces).
354404
pub fn rewrite_missing_comment(

src/expr.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1955,7 +1955,11 @@ fn rewrite_string_lit(context: &RewriteContext, span: Span, shape: Shape) -> Opt
19551955
// Remove the quote characters.
19561956
let str_lit = &string_lit[1..string_lit.len() - 1];
19571957

1958-
rewrite_string(str_lit, &StringFormat::new(shape, context.config))
1958+
rewrite_string(
1959+
str_lit,
1960+
&StringFormat::new(shape.visual_indent(0), context.config),
1961+
None,
1962+
)
19591963
}
19601964

19611965
fn string_requires_rewrite(

src/string.rs

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,13 +44,17 @@ impl<'a> StringFormat<'a> {
4444
}
4545

4646
// FIXME: simplify this!
47-
pub fn rewrite_string<'a>(orig: &str, fmt: &StringFormat<'a>) -> Option<String> {
47+
pub fn rewrite_string<'a>(
48+
orig: &str,
49+
fmt: &StringFormat<'a>,
50+
max_width: Option<usize>,
51+
) -> Option<String> {
4852
// Strip line breaks.
4953
let re = Regex::new(r"([^\\](\\\\)*)\\[\n\r][[:space:]]*").unwrap();
5054
let stripped_str = re.replace_all(orig, "$1");
5155

5256
let graphemes = UnicodeSegmentation::graphemes(&*stripped_str, false).collect::<Vec<&str>>();
53-
let shape = fmt.shape.visual_indent(0);
57+
let shape = fmt.shape;
5458
let indent = shape.indent.to_string(fmt.config);
5559
let punctuation = ":,;.";
5660

@@ -67,7 +71,7 @@ pub fn rewrite_string<'a>(orig: &str, fmt: &StringFormat<'a>) -> Option<String>
6771
let ender_length = fmt.line_end.len();
6872
// If we cannot put at least a single character per line, the rewrite won't
6973
// succeed.
70-
let max_chars = shape
74+
let mut max_chars = shape
7175
.width
7276
.checked_sub(fmt.opener.len() + ender_length + 1)? + 1;
7377

@@ -135,6 +139,10 @@ pub fn rewrite_string<'a>(orig: &str, fmt: &StringFormat<'a>) -> Option<String>
135139

136140
// The next line starts where the current line ends.
137141
cur_start = cur_end;
142+
143+
if let Some(new_max_chars) = max_width {
144+
max_chars = new_max_chars.checked_sub(fmt.opener.len() + ender_length + 1)? + 1;
145+
}
138146
}
139147

140148
result.push_str(fmt.closer);
@@ -150,6 +158,6 @@ mod test {
150158
fn issue343() {
151159
let config = Default::default();
152160
let fmt = StringFormat::new(Shape::legacy(2, Indent::empty()), &config);
153-
rewrite_string("eq_", &fmt);
161+
rewrite_string("eq_", &fmt, None);
154162
}
155163
}

tests/source/soft-wrapping.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// rustfmt-wrap_comments: true
2+
// rustfmt-max_width: 80
3+
// Soft wrapping for comments.
4+
5+
// #535, soft wrapping for comments
6+
// Compare the lowest `f32` of both inputs for greater than or equal. The
7+
// lowest 32 bits of the result will be `0xffffffff` if `a.extract(0)` is
8+
// ggreater than or equal `b.extract(0)`, or `0` otherwise. The upper 96 bits off
9+
// the result are the upper 96 bits of `a`.

tests/target/soft-wrapping.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// rustfmt-wrap_comments: true
2+
// rustfmt-max_width: 80
3+
// Soft wrapping for comments.
4+
5+
// #535, soft wrapping for comments
6+
// Compare the lowest `f32` of both inputs for greater than or equal. The
7+
// lowest 32 bits of the result will be `0xffffffff` if `a.extract(0)` is
8+
// ggreater than or equal `b.extract(0)`, or `0` otherwise. The upper 96 bits
9+
// off the result are the upper 96 bits of `a`.

0 commit comments

Comments
 (0)