rust/src/lists.rs

163 lines
5 KiB
Rust
Raw Normal View History

// 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 11:01:19 +02:00
use utils::make_indent;
#[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-04-23 07:04:07 +02:00
// TODO having some helpful ctors for ListFormatting would be nice.
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 12:50:43 +02:00
// Precondition: all strings in items are trimmed.
pub fn write_list<'b>(items: &[(String, String)], formatting: &ListFormatting<'b>) -> String {
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 12:50:43 +02:00
let sep_len = formatting.separator.len();
let total_sep_len = (sep_len + 1) * sep_count;
2015-04-21 12:50:43 +02:00
let total_width = calculate_width(items);
// Check if we need to fallback from horizontal listing, if possible.
2015-04-23 08:02:55 +02:00
if tactic == ListTactic::HorizontalVertical {
2015-04-21 12:50:43 +02:00
if total_width + total_sep_len > formatting.h_width {
tactic = ListTactic::Vertical;
} else {
tactic = ListTactic::Horizontal;
}
}
// Now that we know how we will layout, we can decide for sure if there
// will be a trailing separator.
2015-04-21 12:50:43 +02:00
let trailing_separator = needs_trailing_separator(formatting.trailing_separator, tactic);
// 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 12:50:43 +02:00
total_width + total_sep_len
} 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 12:50:43 +02:00
for (i, &(ref item, ref comment)) in items.iter().enumerate() {
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 12:50:43 +02:00
if line_len > 0 && line_len + item_width > formatting.v_width {
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 08:02:55 +02:00
2015-04-21 12:50:43 +02:00
if tactic != ListTactic::Vertical && comment.len() > 0 {
result.push(' ');
result.push_str(comment);
}
if separate {
result.push_str(formatting.separator);
}
2015-04-21 12:50:43 +02:00
if tactic == ListTactic::Vertical && comment.len() > 0 {
result.push(' ');
result.push_str(comment);
}
}
result
}
2015-04-21 12:50:43 +02: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
}