chiark / gitweb /
First half of FileStatusLine. Rendering undone.
authorSimon Tatham <anakin@pobox.com>
Tue, 26 Dec 2023 10:18:18 +0000 (10:18 +0000)
committerSimon Tatham <anakin@pobox.com>
Tue, 26 Dec 2023 10:24:27 +0000 (10:24 +0000)
src/text.rs

index 49fa81946db551f8c970dca4c47bd9b15b79e45c..651ffd4e957f14fcb0600f553f5be17be93ea033 100644 (file)
@@ -2,7 +2,8 @@ use chrono::{DateTime, Local, Utc};
 #[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};
@@ -1049,6 +1050,157 @@ fn test_media() {
         });
 }
 
+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