2015-04-21 16:47:15 +12:00
|
|
|
// 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 <LICENSE-APACHE or
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
|
|
|
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
|
|
|
// option. This file may not be copied, modified, or distributed
|
|
|
|
// except according to those terms.
|
|
|
|
|
2015-04-21 21:01:19 +12:00
|
|
|
use utils::make_indent;
|
2015-05-25 19:11:53 +12:00
|
|
|
use rustc_serialize::{Decodable, Decoder};
|
2015-04-21 16:47:15 +12:00
|
|
|
|
|
|
|
#[derive(Eq, PartialEq, Debug, Copy, Clone)]
|
|
|
|
pub enum ListTactic {
|
|
|
|
// One item per row.
|
|
|
|
Vertical,
|
|
|
|
// All items on one row.
|
|
|
|
Horizontal,
|
|
|
|
// Try Horizontal layout, if that fails then vertical
|
|
|
|
HorizontalVertical,
|
|
|
|
// Pack as many items as possible per row over (possibly) many rows.
|
|
|
|
Mixed,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Eq, PartialEq, Debug, Copy, Clone)]
|
|
|
|
pub enum SeparatorTactic {
|
|
|
|
Always,
|
|
|
|
Never,
|
|
|
|
Vertical,
|
|
|
|
}
|
|
|
|
|
2015-05-25 19:11:53 +12:00
|
|
|
// TODO could use a macro for all these Decodable impls.
|
|
|
|
impl Decodable for SeparatorTactic {
|
|
|
|
fn decode<D: Decoder>(d: &mut D) -> Result<Self, D::Error> {
|
|
|
|
let s = try!(d.read_str());
|
|
|
|
match &*s {
|
|
|
|
"Always" => Ok(SeparatorTactic::Always),
|
|
|
|
"Never" => Ok(SeparatorTactic::Never),
|
|
|
|
"Vertical" => Ok(SeparatorTactic::Vertical),
|
|
|
|
_ => Err(d.error("Bad variant")),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-04-23 17:04:07 +12:00
|
|
|
// TODO having some helpful ctors for ListFormatting would be nice.
|
2015-04-21 16:47:15 +12:00
|
|
|
pub struct ListFormatting<'a> {
|
|
|
|
pub tactic: ListTactic,
|
|
|
|
pub separator: &'a str,
|
|
|
|
pub trailing_separator: SeparatorTactic,
|
|
|
|
pub indent: usize,
|
|
|
|
// Available width if we layout horizontally.
|
|
|
|
pub h_width: usize,
|
|
|
|
// Available width if we layout vertically
|
|
|
|
pub v_width: usize,
|
|
|
|
}
|
|
|
|
|
|
|
|
// Format a list of strings into a string.
|
2015-04-21 22:50:43 +12:00
|
|
|
// Precondition: all strings in items are trimmed.
|
|
|
|
pub fn write_list<'b>(items: &[(String, String)], formatting: &ListFormatting<'b>) -> String {
|
2015-04-21 16:47:15 +12:00
|
|
|
if items.len() == 0 {
|
|
|
|
return String::new();
|
|
|
|
}
|
|
|
|
|
|
|
|
let mut tactic = formatting.tactic;
|
|
|
|
|
|
|
|
// Conservatively overestimates because of the changing separator tactic.
|
|
|
|
let sep_count = if formatting.trailing_separator != SeparatorTactic::Never {
|
|
|
|
items.len()
|
|
|
|
} else {
|
|
|
|
items.len() - 1
|
|
|
|
};
|
2015-04-21 22:50:43 +12:00
|
|
|
let sep_len = formatting.separator.len();
|
|
|
|
let total_sep_len = (sep_len + 1) * sep_count;
|
|
|
|
let total_width = calculate_width(items);
|
2015-05-04 17:16:51 +02:00
|
|
|
let fits_single = total_width + total_sep_len <= formatting.h_width;
|
2015-04-21 16:47:15 +12:00
|
|
|
|
|
|
|
// Check if we need to fallback from horizontal listing, if possible.
|
2015-04-23 18:02:55 +12:00
|
|
|
if tactic == ListTactic::HorizontalVertical {
|
2015-04-23 18:30:12 +12:00
|
|
|
debug!("write_list: total_width: {}, total_sep_len: {}, h_width: {}",
|
|
|
|
total_width, total_sep_len, formatting.h_width);
|
2015-06-04 13:47:35 +02:00
|
|
|
tactic = if fits_single &&
|
|
|
|
!items.iter().any(|&(ref s, _)| s.contains('\n')) {
|
2015-05-04 17:16:51 +02:00
|
|
|
ListTactic::Horizontal
|
2015-04-21 16:47:15 +12:00
|
|
|
} else {
|
2015-05-04 17:16:51 +02:00
|
|
|
ListTactic::Vertical
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check if we can fit everything on a single line in mixed mode.
|
|
|
|
// The horizontal tactic does not break after v_width columns.
|
|
|
|
if tactic == ListTactic::Mixed && fits_single {
|
|
|
|
tactic = ListTactic::Horizontal;
|
2015-04-21 16:47:15 +12:00
|
|
|
}
|
|
|
|
|
|
|
|
// Now that we know how we will layout, we can decide for sure if there
|
|
|
|
// will be a trailing separator.
|
2015-04-21 22:50:43 +12:00
|
|
|
let trailing_separator = needs_trailing_separator(formatting.trailing_separator, tactic);
|
2015-04-21 16:47:15 +12:00
|
|
|
|
|
|
|
// Create a buffer for the result.
|
|
|
|
// TODO could use a StringBuffer or rope for this
|
|
|
|
let alloc_width = if tactic == ListTactic::Horizontal {
|
2015-04-21 22:50:43 +12:00
|
|
|
total_width + total_sep_len
|
2015-04-21 16:47:15 +12:00
|
|
|
} else {
|
|
|
|
total_width + items.len() * (formatting.indent + 1)
|
|
|
|
};
|
|
|
|
let mut result = String::with_capacity(alloc_width);
|
|
|
|
|
|
|
|
let mut line_len = 0;
|
|
|
|
let indent_str = &make_indent(formatting.indent);
|
2015-04-21 22:50:43 +12:00
|
|
|
for (i, &(ref item, ref comment)) in items.iter().enumerate() {
|
2015-04-21 16:47:15 +12:00
|
|
|
let first = i == 0;
|
|
|
|
let separate = i != items.len() - 1 || trailing_separator;
|
|
|
|
|
|
|
|
match tactic {
|
|
|
|
ListTactic::Horizontal if !first => {
|
|
|
|
result.push(' ');
|
|
|
|
}
|
|
|
|
ListTactic::Vertical if !first => {
|
|
|
|
result.push('\n');
|
|
|
|
result.push_str(indent_str);
|
|
|
|
}
|
|
|
|
ListTactic::Mixed => {
|
|
|
|
let mut item_width = item.len();
|
|
|
|
if separate {
|
|
|
|
item_width += sep_len;
|
|
|
|
}
|
|
|
|
|
2015-04-21 22:50:43 +12:00
|
|
|
if line_len > 0 && line_len + item_width > formatting.v_width {
|
2015-04-21 16:47:15 +12:00
|
|
|
result.push('\n');
|
|
|
|
result.push_str(indent_str);
|
|
|
|
line_len = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if line_len > 0 {
|
|
|
|
result.push(' ');
|
|
|
|
line_len += 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
line_len += item_width;
|
|
|
|
}
|
|
|
|
_ => {}
|
|
|
|
}
|
|
|
|
|
|
|
|
result.push_str(item);
|
2015-04-23 18:02:55 +12:00
|
|
|
|
2015-04-21 22:50:43 +12:00
|
|
|
if tactic != ListTactic::Vertical && comment.len() > 0 {
|
2015-04-28 21:57:16 +12:00
|
|
|
if !comment.starts_with('\n') {
|
|
|
|
result.push(' ');
|
|
|
|
}
|
2015-04-21 22:50:43 +12:00
|
|
|
result.push_str(comment);
|
|
|
|
}
|
|
|
|
|
2015-04-21 16:47:15 +12:00
|
|
|
if separate {
|
|
|
|
result.push_str(formatting.separator);
|
|
|
|
}
|
2015-04-21 22:50:43 +12:00
|
|
|
|
|
|
|
if tactic == ListTactic::Vertical && comment.len() > 0 {
|
2015-04-28 21:57:16 +12:00
|
|
|
if !comment.starts_with('\n') {
|
|
|
|
result.push(' ');
|
|
|
|
}
|
2015-04-21 22:50:43 +12:00
|
|
|
result.push_str(comment);
|
|
|
|
}
|
2015-04-21 16:47:15 +12:00
|
|
|
}
|
|
|
|
|
|
|
|
result
|
|
|
|
}
|
2015-04-21 22:50:43 +12:00
|
|
|
|
|
|
|
fn needs_trailing_separator(separator_tactic: SeparatorTactic, list_tactic: ListTactic) -> bool {
|
|
|
|
match separator_tactic {
|
|
|
|
SeparatorTactic::Always => true,
|
|
|
|
SeparatorTactic::Vertical => list_tactic == ListTactic::Vertical,
|
|
|
|
SeparatorTactic::Never => false,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn calculate_width(items:&[(String, String)]) -> usize {
|
|
|
|
let missed_width = items.iter().map(|&(_, ref s)| {
|
|
|
|
let text_len = s.trim().len();
|
|
|
|
if text_len > 0 {
|
|
|
|
// We'll put a space before any comment.
|
|
|
|
text_len + 1
|
|
|
|
} else {
|
|
|
|
text_len
|
|
|
|
}
|
|
|
|
}).fold(0, |a, l| a + l);
|
|
|
|
let item_width = items.iter().map(|&(ref s, _)| s.len()).fold(0, |a, l| a + l);
|
|
|
|
missed_width + item_width
|
|
|
|
}
|