// 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 regex::Regex; use unicode_segmentation::UnicodeSegmentation; use config::Config; use shape::Shape; use utils::wrap_str; const MIN_STRING: usize = 10; 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, } impl<'a> StringFormat<'a> { pub fn new(shape: Shape, config: &'a Config) -> StringFormat<'a> { StringFormat { opener: "\"", closer: "\"", line_start: " ", line_end: "\\", shape, trim_end: false, config, } } } // FIXME: simplify this! pub fn rewrite_string<'a>( orig: &str, fmt: &StringFormat<'a>, max_width: Option, ) -> 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; let indent = shape.indent.to_string_with_newline(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 mut max_chars = 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_str(&indent); result.push_str(fmt.line_start); // The next line starts where the current line ends. cur_start = cur_end; if let Some(new_max_chars) = max_width { max_chars = new_max_chars.checked_sub(fmt.opener.len() + ender_length + 1)? + 1; } } result.push_str(fmt.closer); wrap_str(result, fmt.config.max_width(), fmt.shape) } #[cfg(test)] mod test { use super::{rewrite_string, StringFormat}; use shape::{Indent, Shape}; #[test] fn issue343() { let config = Default::default(); let fmt = StringFormat::new(Shape::legacy(2, Indent::empty()), &config); rewrite_string("eq_", &fmt, None); } }