]> git.lizzy.rs Git - rust.git/commitdiff
Implement soft wrapping on doc comments
authortopecongiro <seuchida@gmail.com>
Wed, 8 Nov 2017 09:24:03 +0000 (18:24 +0900)
committertopecongiro <seuchida@gmail.com>
Wed, 8 Nov 2017 09:24:03 +0000 (18:24 +0900)
src/visitor.rs

index 417b12b2d53215686ebdc3cbd484f1c8c3fb269a..a3fc1218145c4b7460e3f0dd4208807130b568fe 100644 (file)
@@ -19,7 +19,7 @@
 use expr::rewrite_literal;
 use spanned::Spanned;
 use codemap::{LineRangeUtils, SpanUtils};
-use comment::{contains_comment, recover_missing_comment_in_span, remove_trailing_white_spaces,
+use comment::{combine_strs_with_missing_comments, contains_comment, remove_trailing_white_spaces,
               CodeCharKind, CommentCodeSlices, FindUncommented};
 use comment::rewrite_comment;
 use config::{BraceStyle, Config};
@@ -791,106 +791,149 @@ fn rewrite(&self, context: &RewriteContext, shape: Shape) -> Option<String> {
     }
 }
 
+/// Returns the first group of attributes that fills the given predicate.
+/// We consider two doc comments are in different group if they are separated by normal comments.
+fn take_while_with_pred<'a, P>(
+    context: &RewriteContext,
+    attrs: &'a [ast::Attribute],
+    pred: P,
+) -> Option<&'a [ast::Attribute]>
+where
+    P: Fn(&ast::Attribute) -> bool,
+{
+    let mut last_index = 0;
+    let mut iter = attrs.iter().enumerate().peekable();
+    while let Some((i, attr)) = iter.next() {
+        if !pred(attr) {
+            break;
+        }
+        if let Some(&(_, next_attr)) = iter.peek() {
+            // Extract comments between two attributes.
+            let span_between_attr = mk_sp(attr.span.hi(), next_attr.span.lo());
+            let snippet = context.snippet(span_between_attr);
+            if snippet.chars().filter(|c| *c == '\n').count() >= 2 || snippet.contains('/') {
+                break;
+            }
+        }
+        last_index = i;
+    }
+    if last_index == 0 {
+        None
+    } else {
+        Some(&attrs[..last_index + 1])
+    }
+}
+
+fn rewrite_first_group_attrs(
+    context: &RewriteContext,
+    attrs: &[ast::Attribute],
+    shape: Shape,
+) -> Option<(usize, String)> {
+    if attrs.is_empty() {
+        return Some((0, String::new()));
+    }
+    // Rewrite doc comments
+    match take_while_with_pred(context, attrs, |a| a.is_sugared_doc) {
+        Some(sugared_docs) if !sugared_docs.is_empty() => {
+            let snippet = sugared_docs
+                .iter()
+                .map(|a| context.snippet(a.span))
+                .collect::<Vec<_>>()
+                .join("\n");
+            return Some((
+                sugared_docs.len(),
+                rewrite_comment(&snippet, false, shape, context.config)?,
+            ));
+        }
+        _ => (),
+    }
+    // Rewrite `#[derive(..)]`s.
+    if context.config.merge_derives() {
+        match take_while_with_pred(context, attrs, is_derive) {
+            Some(derives) if !derives.is_empty() => {
+                let mut derive_args = vec![];
+                for derive in derives {
+                    derive_args.append(&mut get_derive_args(context, derive)?);
+                }
+                return Some((
+                    derives.len(),
+                    format_derive(context, &derive_args, shape)?,
+                ));
+            }
+            _ => (),
+        }
+    }
+    // Rewrite the first attribute.
+    Some((1, attrs[0].rewrite(context, shape)?))
+}
+
+fn has_newlines_before_after_comment(comment: &str) -> (&str, &str) {
+    // Look at before and after comment and see if there are any empty lines.
+    let comment_begin = comment.chars().position(|c| c == '/');
+    let len = comment_begin.unwrap_or_else(|| comment.len());
+    let mlb = comment.chars().take(len).filter(|c| *c == '\n').count() > 1;
+    let mla = if comment_begin.is_none() {
+        mlb
+    } else {
+        let comment_end = comment.chars().rev().position(|c| !c.is_whitespace());
+        let len = comment_end.unwrap();
+        comment
+            .chars()
+            .rev()
+            .take(len)
+            .filter(|c| *c == '\n')
+            .count() > 1
+    };
+    (if mlb { "\n" } else { "" }, if mla { "\n" } else { "" })
+}
+
 impl<'a> Rewrite for [ast::Attribute] {
     fn rewrite(&self, context: &RewriteContext, shape: Shape) -> Option<String> {
         if self.is_empty() {
             return Some(String::new());
         }
-        let mut result = String::with_capacity(128);
-        let indent = shape.indent.to_string(context.config);
-
-        let mut derive_args = Vec::new();
-
-        let mut iter = self.iter().enumerate().peekable();
-        let mut insert_new_line = true;
-        let mut is_prev_sugared_doc = false;
-        while let Some((i, a)) = iter.next() {
-            let a_str = a.rewrite(context, shape)?;
-
-            // Write comments and blank lines between attributes.
-            if i > 0 {
-                let comment = context.snippet(mk_sp(self[i - 1].span.hi(), a.span.lo()));
-                // This particular horror show is to preserve line breaks in between doc
-                // comments. An alternative would be to force such line breaks to start
-                // with the usual doc comment token.
-                let (multi_line_before, multi_line_after) = if a.is_sugared_doc
-                    || is_prev_sugared_doc
-                {
-                    // Look at before and after comment and see if there are any empty lines.
-                    let comment_begin = comment.chars().position(|c| c == '/');
-                    let len = comment_begin.unwrap_or_else(|| comment.len());
-                    let mlb = comment.chars().take(len).filter(|c| *c == '\n').count() > 1;
-                    let mla = if comment_begin.is_none() {
-                        mlb
-                    } else {
-                        let comment_end = comment.chars().rev().position(|c| !c.is_whitespace());
-                        let len = comment_end.unwrap();
-                        comment
-                            .chars()
-                            .rev()
-                            .take(len)
-                            .filter(|c| *c == '\n')
-                            .count() > 1
-                    };
-                    (mlb, mla)
-                } else {
-                    (false, false)
-                };
-
-                let comment = recover_missing_comment_in_span(
-                    mk_sp(self[i - 1].span.hi(), a.span.lo()),
+        let (first_group_len, first_group_str) = rewrite_first_group_attrs(context, self, shape)?;
+        if self.len() == 1 || first_group_len == self.len() {
+            Some(first_group_str)
+        } else {
+            let rest_str = self[first_group_len..].rewrite(context, shape)?;
+            let missing_span = mk_sp(
+                self[first_group_len - 1].span.hi(),
+                self[first_group_len].span.lo(),
+            );
+            // Preserve an empty line before/after doc comments.
+            if self[0].is_sugared_doc || self[first_group_len].is_sugared_doc {
+                let snippet = context.snippet(missing_span);
+                let (mla, mlb) = has_newlines_before_after_comment(&snippet);
+                let comment = ::comment::recover_missing_comment_in_span(
+                    missing_span,
                     shape.with_max_width(context.config),
                     context,
                     0,
                 )?;
-
-                if !comment.is_empty() {
-                    if multi_line_before {
-                        result.push('\n');
-                    }
-                    result.push_str(&comment);
-                    result.push('\n');
-                    if multi_line_after {
-                        result.push('\n')
-                    }
-                } else if insert_new_line {
-                    result.push('\n');
-                    if multi_line_after {
-                        result.push('\n')
-                    }
-                }
-
-                if derive_args.is_empty() {
-                    result.push_str(&indent);
-                }
-
-                insert_new_line = true;
-            }
-
-            // Write the attribute itself.
-            if context.config.merge_derives() {
-                // If the attribute is `#[derive(...)]`, take the arguments.
-                if let Some(mut args) = get_derive_args(context, a) {
-                    derive_args.append(&mut args);
-                    match iter.peek() {
-                        // If the next attribute is `#[derive(...)]` as well, skip rewriting.
-                        Some(&(_, next_attr)) if is_derive(next_attr) => insert_new_line = false,
-                        // If not, rewrite the merged derives.
-                        _ => {
-                            result.push_str(&format_derive(context, &derive_args, shape)?);
-                            derive_args.clear();
-                        }
-                    }
+                let comment = if comment.is_empty() {
+                    format!("\n{}", mlb)
                 } else {
-                    result.push_str(&a_str);
-                }
+                    format!("{}{}\n{}", mla, comment, mlb)
+                };
+                Some(format!(
+                    "{}{}{}{}",
+                    first_group_str,
+                    comment,
+                    shape.indent.to_string(context.config),
+                    rest_str
+                ))
             } else {
-                result.push_str(&a_str);
+                combine_strs_with_missing_comments(
+                    context,
+                    &first_group_str,
+                    &rest_str,
+                    missing_span,
+                    shape,
+                    false,
+                )
             }
-
-            is_prev_sugared_doc = a.is_sugared_doc;
         }
-        Some(result)
     }
 }