use rewrite::RewriteContext;
use shape::{Indent, Shape};
use string::{rewrite_string, StringFormat};
-use utils::{first_line_width, last_line_width};
+use utils::{count_newlines, first_line_width, last_line_width};
fn is_custom_comment(comment: &str) -> bool {
if !comment.starts_with("//") {
pub fn closer(&self) -> &'a str {
match *self {
- CommentStyle::DoubleSlash |
- CommentStyle::TripleSlash |
- CommentStyle::Custom(..) |
- CommentStyle::Doc => "",
+ CommentStyle::DoubleSlash
+ | CommentStyle::TripleSlash
+ | CommentStyle::Custom(..)
+ | CommentStyle::Doc => "",
CommentStyle::DoubleBullet => " **/",
CommentStyle::SingleBullet | CommentStyle::Exclamation => " */",
}
// We have a missing comment between the first expression and the second expression.
// Peek the the original source code and find out whether there is a newline between the first
- // expression and the second expression or the missing comment. We will preserve the orginal
+ // expression and the second expression or the missing comment. We will preserve the original
// layout whenever possible.
let original_snippet = context.snippet(span);
let prefer_same_line = if let Some(pos) = original_snippet.chars().position(|c| c == '/') {
};
Some(format!(
"{}{}{}{}{}",
- prev_str,
- first_sep,
- missing_comment,
- second_sep,
- next_str,
+ prev_str, first_sep, missing_comment, second_sep, next_str,
))
}
// we should stop now.
let num_bare_lines = orig.lines()
.map(|line| line.trim())
- .filter(|l| {
- !(l.starts_with('*') || l.starts_with("//") || l.starts_with("/*"))
- })
+ .filter(|l| !(l.starts_with('*') || l.starts_with("//") || l.starts_with("/*")))
.count();
if num_bare_lines > 0 && !config.normalize_comments() {
return Some(orig.to_owned());
.checked_sub(closer.len() + opener.len())
.unwrap_or(1);
let indent_str = shape.indent.to_string(config);
- let fmt = StringFormat {
+ let fmt_indent = shape.indent + (opener.len() - line_start.len());
+ let mut fmt = StringFormat {
opener: "",
closer: "",
line_start: line_start,
line_end: "",
- shape: Shape::legacy(max_chars, shape.indent + (opener.len() - line_start.len())),
+ shape: Shape::legacy(max_chars, fmt_indent),
trim_end: true,
config: config,
};
- let line_breaks = orig.trim_right().chars().filter(|&c| c == '\n').count();
+ let line_breaks = count_newlines(orig.trim_right());
let lines = orig.lines()
.enumerate()
.map(|(i, mut line)| {
line
})
.map(|s| left_trim_comment_line(s, &style))
- .map(|line| if orig.starts_with("/*") && line_breaks == 0 {
- line.trim_left()
- } else {
- line
+ .map(|line| {
+ if orig.starts_with("/*") && line_breaks == 0 {
+ line.trim_left()
+ } else {
+ line
+ }
});
let mut result = opener.to_owned();
+ let mut is_prev_line_multi_line = false;
+ let mut inside_code_block = false;
+ let comment_line_separator = format!("\n{}{}", indent_str, line_start);
for line in lines {
if result == opener {
if line.is_empty() {
continue;
}
+ } else if is_prev_line_multi_line && !line.is_empty() {
+ result.push(' ')
} else {
- result.push('\n');
- result.push_str(&indent_str);
- result.push_str(line_start);
+ result.push_str(&comment_line_separator);
+ }
+
+ if line.starts_with("```") {
+ inside_code_block = !inside_code_block;
+ }
+ if inside_code_block {
+ if line.is_empty() && result.ends_with(' ') {
+ result.pop();
+ } else {
+ result.push_str(line);
+ }
+ continue;
}
- if config.wrap_comments() && line.len() > max_chars {
- let rewrite = rewrite_string(line, &fmt).unwrap_or_else(|| line.to_owned());
- result.push_str(&rewrite);
+ if config.wrap_comments() && line.len() > fmt.shape.width && !has_url(line) {
+ match rewrite_string(line, &fmt, Some(max_chars)) {
+ Some(ref s) => {
+ is_prev_line_multi_line = s.contains('\n');
+ result.push_str(s);
+ }
+ None if is_prev_line_multi_line => {
+ // We failed to put the current `line` next to the previous `line`.
+ // Remove the trailing space, then start rewrite on the next line.
+ result.pop();
+ result.push_str(&comment_line_separator);
+ fmt.shape = Shape::legacy(max_chars, fmt_indent);
+ match rewrite_string(line, &fmt, Some(max_chars)) {
+ Some(ref s) => {
+ is_prev_line_multi_line = s.contains('\n');
+ result.push_str(s);
+ }
+ None => {
+ is_prev_line_multi_line = false;
+ result.push_str(line);
+ }
+ }
+ }
+ None => {
+ is_prev_line_multi_line = false;
+ result.push_str(line);
+ }
+ }
+
+ fmt.shape = if is_prev_line_multi_line {
+ // 1 = " "
+ let offset = 1 + last_line_width(&result) - line_start.len();
+ Shape {
+ width: max_chars.checked_sub(offset).unwrap_or(0),
+ indent: fmt_indent,
+ offset: fmt.shape.offset + offset,
+ }
+ } else {
+ Shape::legacy(max_chars, fmt_indent)
+ };
} else {
if line.is_empty() && result.ends_with(' ') {
// Remove space if this is an empty comment or a doc comment.
result.pop();
}
result.push_str(line);
+ fmt.shape = Shape::legacy(max_chars, fmt_indent);
+ is_prev_line_multi_line = false;
}
}
Some(result)
}
+/// Returns true if the given string MAY include URLs or alike.
+fn has_url(s: &str) -> bool {
+ // This function may return false positive, but should get its job done in most cases.
+ s.contains("https://") || s.contains("http://") || s.contains("ftp://") || s.contains("file://")
+}
+
/// Given the span, rewrite the missing comment inside it if available.
/// Note that the given span must only include comments (or leading/trailing whitespaces).
pub fn rewrite_missing_comment(
buffer
}
-struct CharClasses<T>
+pub struct CharClasses<T>
where
T: Iterator,
T::Item: RichChar,
status: CharClassesStatus,
}
-trait RichChar {
+pub trait RichChar {
fn get_char(&self) -> char;
}
}
}
+impl RichChar for (char, usize) {
+ fn get_char(&self) -> char {
+ self.0
+ }
+}
+
#[derive(PartialEq, Eq, Debug, Clone, Copy)]
enum CharClassesStatus {
Normal,
/// describing opening and closing of comments for ease when chunking
/// code from tagged characters
#[derive(PartialEq, Eq, Debug, Clone, Copy)]
-enum FullCodeCharKind {
+pub enum FullCodeCharKind {
Normal,
/// The first character of a comment, there is only one for a comment (always '/')
StartComment,
}
impl FullCodeCharKind {
- fn is_comment(&self) -> bool {
+ pub fn is_comment(&self) -> bool {
match *self {
- FullCodeCharKind::StartComment |
- FullCodeCharKind::InComment |
- FullCodeCharKind::EndComment => true,
+ FullCodeCharKind::StartComment
+ | FullCodeCharKind::InComment
+ | FullCodeCharKind::EndComment => true,
_ => false,
}
}
+ pub fn is_string(&self) -> bool {
+ *self == FullCodeCharKind::InString
+ }
+
fn to_codecharkind(&self) -> CodeCharKind {
if self.is_comment() {
CodeCharKind::Comment
T: Iterator,
T::Item: RichChar,
{
- fn new(base: T) -> CharClasses<T> {
+ pub fn new(base: T) -> CharClasses<T> {
CharClasses {
base: base.peekable(),
status: CharClassesStatus::Normal,
}
}
-
-
-
/// Iterator over an alternating sequence of functional and commented parts of
/// a string. The first item is always a, possibly zero length, subslice of
/// functional text. Line style comments contain their ending newlines.
context: &RewriteContext,
) -> Option<String> {
let snippet = context.snippet(span);
- if snippet != new && changed_comment_content(&snippet, &new) {
+ if snippet != new && changed_comment_content(snippet, &new) {
// We missed some comments. Keep the original text.
- Some(snippet)
+ Some(snippet.to_owned())
} else {
Some(new)
}
res
}
-
/// Iterator over the 'payload' characters of a comment.
/// It skips whitespace, comment start/end marks, and '*' at the beginning of lines.
/// The comment must be one comment, ie not more than one start mark (no multiple line comments,
}
}
-
fn remove_comment_header(comment: &str) -> &str {
if comment.starts_with("///") || comment.starts_with("//!") {
&comment[3..]