// Copyright 2015 The Rust Project Developers. See the COPYRIGHT // file at the top-level directory of this distribution and at // http://rust-lang.org/COPYRIGHT. // // Licensed under the Apache License, Version 2.0 or the MIT license // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. // Format string literals. use unicode_segmentation::UnicodeSegmentation; use regex::Regex; use Shape; use config::Config; use utils::wrap_str; use MIN_STRING; pub struct StringFormat<'a> { pub opener: &'a str, pub closer: &'a str, pub line_start: &'a str, pub line_end: &'a str, pub shape: Shape, pub trim_end: bool, pub config: &'a Config, } // FIXME: simplify this! pub fn rewrite_string<'a>(orig: &str, fmt: &StringFormat<'a>) -> Option { // Strip line breaks. let re = Regex::new(r"([^\\](\\\\)*)\\[\n\r][[:space:]]*").unwrap(); let stripped_str = re.replace_all(orig, "$1"); let graphemes = UnicodeSegmentation::graphemes(&*stripped_str, false).collect::>(); let shape = fmt.shape.visual_indent(0); let indent = shape.indent.to_string(fmt.config); let punctuation = ":,;."; // `cur_start` is the position in `orig` of the start of the current line. let mut cur_start = 0; let mut result = String::with_capacity(stripped_str .len() .checked_next_power_of_two() .unwrap_or(usize::max_value())); result.push_str(fmt.opener); let ender_length = fmt.line_end.len(); // If we cannot put at least a single character per line, the rewrite won't // succeed. let max_chars = try_opt!(shape.width.checked_sub(fmt.opener.len() + ender_length + 1)) + 1; // Snip a line at a time from `orig` until it is used up. Push the snippet // onto result. 'outer: loop { // `cur_end` will be where we break the line, as an offset into `orig`. // Initialised to the maximum it could be (which may be beyond `orig`). let mut cur_end = cur_start + max_chars; // We can fit the rest of the string on this line, so we're done. if cur_end >= graphemes.len() { let line = &graphemes[cur_start..].join(""); result.push_str(line); break 'outer; } // Push cur_end left until we reach whitespace (or the line is too small). while !graphemes[cur_end - 1].trim().is_empty() { cur_end -= 1; if cur_end < cur_start + MIN_STRING { // We couldn't find whitespace before the string got too small. // So start again at the max length and look for punctuation. cur_end = cur_start + max_chars; while !punctuation.contains(graphemes[cur_end - 1]) { cur_end -= 1; // If we can't break at whitespace or punctuation, grow the string instead. if cur_end < cur_start + MIN_STRING { cur_end = cur_start + max_chars; while !(punctuation.contains(graphemes[cur_end - 1]) || graphemes[cur_end - 1].trim().is_empty()) { if cur_end >= graphemes.len() { let line = &graphemes[cur_start..].join(""); result.push_str(line); break 'outer; } cur_end += 1; } break; } } break; } } // Make sure there is no whitespace to the right of the break. while cur_end < stripped_str.len() && graphemes[cur_end].trim().is_empty() { cur_end += 1; } // Make the current line and add it on to result. let raw_line = graphemes[cur_start..cur_end].join(""); let line = if fmt.trim_end { raw_line.trim() } else { raw_line.as_str() }; result.push_str(line); result.push_str(fmt.line_end); result.push('\n'); result.push_str(&indent); result.push_str(fmt.line_start); // The next line starts where the current line ends. cur_start = cur_end; } result.push_str(fmt.closer); wrap_str(result, fmt.config.max_width, fmt.shape) } #[cfg(test)] mod test { use super::{StringFormat, rewrite_string}; #[test] fn issue343() { let config = Default::default(); let fmt = StringFormat { opener: "\"", closer: "\"", line_start: " ", line_end: "\\", shape: ::Shape::legacy(2, ::Indent::empty()), trim_end: false, config: &config, }; rewrite_string("eq_", &fmt); } }