#[cfg(test)]
use chrono::NaiveDateTime;
use core::cmp::min;
-use std::collections::{HashMap, BTreeSet};
+use std::collections::{HashMap, BTreeMap, BTreeSet};
+use unicode_width::UnicodeWidthStr;
use super::html;
use super::coloured_string::{ColouredString, ColouredStringSlice};
});
}
+type Key = String; // FIXME: maybe make this a richer type later so we
+ // can match it against actual keypresses
+
+struct Keypress {
+ key: Key,
+ description: ColouredString,
+}
+
+pub struct FileStatusLine {
+ keypresses: Vec<(Keypress, usize)>, // each with display priority
+ proportion: Option<u32>,
+ message: Option<String>,
+}
+
+struct FileStatusLineFinal {
+ fs: FileStatusLine,
+ priwidth: Vec<(usize, usize)>, // min priority, total width of those keys
+}
+
+impl FileStatusLine {
+ const SPACING: usize = 2;
+
+ pub fn new() -> FileStatusLine {
+ FileStatusLine {
+ keypresses: Vec::new(),
+ proportion: None,
+ message: None,
+ }
+ }
+
+ pub fn set_proportion(mut self, numer: usize, denom: usize) -> Self {
+ self.proportion = Some((100 * numer / denom) as u32);
+ self
+ }
+
+ pub fn message(mut self, msg: &str) -> Self {
+ self.message = Some(msg.to_owned());
+ self
+ }
+
+ pub fn add(mut self, key: Key, description: &str, priority: usize) -> Self {
+ self.keypresses.push((Keypress {
+ key: key,
+ description: ColouredString::plain(description)
+ }, priority));
+ self
+ }
+
+ fn finalise(self) -> FileStatusLineFinal {
+ let mut bypri = BTreeMap::new();
+ for (kp, pri) in &self.keypresses {
+ // [key]:desc
+ let key: &str = &kp.key;
+ let width = UnicodeWidthStr::width(key) +
+ kp.description.width() + 3;
+
+ if let Some(priwidth) = bypri.get_mut(pri) {
+ *priwidth += Self::SPACING + width;
+ } else {
+ bypri.insert(pri, width);
+ }
+ }
+
+ // If the keypresses are the only thing on this status line,
+ // then we don't need an extra SPACING to separate them from
+ // other stuff.
+ let initial = if self.proportion.is_some() ||
+ self.message.is_some() { Self::SPACING } else { 0 };
+
+ let mut cumulative = initial;
+ let mut priwidth = Vec::new();
+ for (minpri, thiswidth) in bypri.iter().rev() {
+ cumulative += thiswidth +
+ if cumulative == initial { 0 } else { Self::SPACING };
+ priwidth.push((**minpri, cumulative));
+ }
+
+ FileStatusLineFinal {
+ fs: self,
+ priwidth: priwidth
+ }
+ }
+
+ pub fn into_box(self) -> Box<dyn TextFragment> {
+ Box::new(self.finalise())
+ }
+}
+
+#[test]
+fn test_filestatus_build() {
+ let fs = FileStatusLine::new().set_proportion(34, 55);
+ assert_eq!(fs.proportion, Some(61));
+
+ let fs = FileStatusLine::new().message("hello, world");
+ assert_eq!(fs.message, Some("hello, world".to_owned()));
+
+ let fsf = FileStatusLine::new()
+ .add("A".to_string(), "Annoy", 10)
+ .finalise();
+ assert_eq!(fsf.priwidth, vec! {
+ (10, 9),
+ });
+
+ let fsf = FileStatusLine::new()
+ .add("A".to_string(), "Annoy", 10)
+ .add("B".to_string(), "Badger", 10)
+ .finalise();
+ assert_eq!(fsf.priwidth, vec! {
+ (10, 9 + 10 + FileStatusLine::SPACING),
+ });
+
+ let fsf = FileStatusLine::new()
+ .add("A".to_string(), "Annoy", 10)
+ .add("B".to_string(), "Badger", 5)
+ .finalise();
+ assert_eq!(fsf.priwidth, vec! {
+ (10, 9),
+ (5, 9 + 10 + FileStatusLine::SPACING),
+ });
+
+ let fsf = FileStatusLine::new()
+ .add("A".to_owned(), "Annoy", 10)
+ .finalise();
+ assert_eq!(fsf.priwidth, vec! {
+ (10, 9),
+ });
+
+ let fsf = FileStatusLine::new()
+ .set_proportion(2, 3)
+ .add("A".to_owned(), "Annoy", 10)
+ .finalise();
+ assert_eq!(fsf.priwidth, vec! {
+ (10, 9 + FileStatusLine::SPACING),
+ });
+
+ let fsf = FileStatusLine::new()
+ .message("aha")
+ .add("A".to_owned(), "Annoy", 10)
+ .finalise();
+ assert_eq!(fsf.priwidth, vec! {
+ (10, 9 + FileStatusLine::SPACING),
+ });
+}
+
+impl TextFragment for FileStatusLineFinal {
+ fn render(&self, _width: usize) -> Vec<ColouredString> {
+ vec! {
+ ColouredString::plain("FIXME"),
+ }
+ }
+}
+
// TODO:
-// FileStatusLine, with priorities
// MenuKeypressLine