use chrono::{DateTime, Utc, Local};
-use core::cmp::{max, min};
+use core::cmp::min;
use std::collections::{HashMap, BTreeSet};
use super::html;
fn as_extendable_indicator(&self) -> Option<&ExtendableIndicator> { None }
}
-pub struct BlankLine {}
+struct BlankLine {}
pub fn blank() -> Box<dyn TextFragment> {
Box::new(BlankLine{})
});
}
-pub struct SeparatorLine {
+struct SeparatorLine {
timestamp: Option<DateTime<Utc>>,
favourited: bool,
boosted: bool,
});
}
-pub struct EditorHeaderSeparator {}
+struct EditorHeaderSeparator {}
pub fn editorsep() -> Box<dyn TextFragment> {
Box::new(EditorHeaderSeparator{})
fn render(&self, width: usize) -> Vec<ColouredString> {
vec! {
ColouredString::uniform(
- &((&"-".repeat(max(0, width - 2))).to_string() + "|"),
+ &((&"-".repeat(width - min(2, width))).to_string() + "|"),
'-',
).truncate(width).to_owned(),
}
});
}
-pub struct UsernameHeader {
+struct UsernameHeader {
header: String,
colour: char,
account: String,
false
}
}
+
+ fn is_colour(&self, colour: char) -> bool {
+ self.frags().all(|(_, c)| c == colour)
+ }
}
impl<'a> ColouredStringSlice<'a> {
self
}
- pub fn push_para(mut self, para: &Paragraph) -> Self {
- let tail = if let Some(last_existing_word) = self.words.last_mut() {
- if let Some(first_new_word) = para.words.first() {
- if last_existing_word.is_space() ==
- first_new_word.is_space() {
- last_existing_word.push_str(&first_new_word.slice());
- 1
- } else { 0 }
- } else { 0 }
- } else { 0 };
- self.words.extend_from_slice(¶.words[tail..]);
- self
+ pub fn push_para(&mut self, para: &Paragraph) {
+ if let Some(word) = self.words.last() {
+ if !word.is_space() {
+ self.push_text(&ColouredString::plain(" "), false);
+ }
+ }
+ self.words.extend_from_slice(¶.words);
+ }
+
+ pub fn delete_mention_words_from(&mut self, start: usize) {
+ let first_non_mention = start + self.words[start..].iter()
+ .position(|word| !(word.is_space() || word.is_colour('@')))
+ .unwrap_or(self.words.len() - start);
+ self.words.splice(start..first_non_mention, vec!{});
}
pub fn into_box(self) -> Box<dyn TextFragment> { Box::new(self) }
});
}
-pub struct FileHeader {
+struct FileHeader {
text: ColouredString,
}
});
}
+pub struct InReplyToLine {
+ para: Paragraph,
+}
+
+pub fn in_reply_to_line(post: &Vec<Paragraph>) -> Box<dyn TextFragment> {
+ let mut para = Paragraph::new().add(&ColouredString::plain("Re: "));
+ let currlen = para.words.len();
+ for cpara in post {
+ para.push_para(cpara);
+ }
+ para.delete_mention_words_from(currlen);
+ Box::new(InReplyToLine {
+ para: para
+ })
+}
+
+impl TextFragment for InReplyToLine {
+ fn render(&self, width: usize) -> Vec<ColouredString> {
+ let rendered_para = self.para.render(width - min(width, 4));
+ let mut it = rendered_para.iter();
+ // "Re:" guarantees the first line must exist at least
+ let first_line = it.next().unwrap();
+ let result = if it.next().is_some() {
+ first_line + ColouredString::plain("...")
+ } else {
+ first_line.clone()
+ };
+ vec! { result.truncate(width).to_owned() }
+ }
+}
+
+#[test]
+fn test_in_reply_to() {
+ let post = vec! {
+ Paragraph::new().add(&ColouredString::general(
+ "@stoat @weasel take a look at this otter!",
+ "@@@@@@ @@@@@@@ ")),
+ Paragraph::new().add(&ColouredString::general(
+ "@badger might also like it",
+ "@@@@@@@ ")),
+ };
+
+ let irt = in_reply_to_line(&post);
+ assert_eq!(irt.render(48), vec!{
+ ColouredString::general(
+ "Re: take a look at this otter! @badger might...",
+ " @@@@@@@ "),
+ });
+ assert_eq!(irt.render(47), vec!{
+ ColouredString::general(
+ "Re: take a look at this otter! @badger...",
+ " @@@@@@@ "),
+ });
+ assert_eq!(irt.render(80), vec!{
+ ColouredString::general(
+ "Re: take a look at this otter! @badger might also like it",
+ " @@@@@@@ "),
+ });
+}
+
// TODO:
-// InReplyToLine, with first line of included paragraph
// NotificationLog, also with included toot
// UserListEntry
// Media