Show state events in the timeline (#437)

This commit is contained in:
Ulyssa
2025-05-30 23:06:19 -07:00
committed by GitHub
parent 998e50f4a5
commit 84eaadc09a
7 changed files with 1145 additions and 79 deletions

View File

@@ -10,10 +10,12 @@
//!
//! This isn't as important for iamb, since it isn't a browser environment, but we do still map
//! input onto an enum of the safe list of tags to keep it easy to understand and process.
use std::borrow::Cow;
use std::ops::Deref;
use css_color_parser::Color as CssColor;
use markup5ever_rcdom::{Handle, NodeData, RcDom};
use matrix_sdk::ruma::{OwnedRoomAliasId, OwnedRoomId, OwnedUserId};
use unicode_segmentation::UnicodeSegmentation;
use url::Url;
@@ -34,6 +36,7 @@ use ratatui::{
};
use crate::{
config::ApplicationSettings,
message::printer::TextPrinter,
util::{join_cell_text, space_text},
};
@@ -148,7 +151,12 @@ impl Table {
}
}
fn to_text(&self, width: usize, style: Style, emoji_shortcodes: bool) -> Text {
fn to_text<'a>(
&'a self,
width: usize,
style: Style,
settings: &'a ApplicationSettings,
) -> Text<'a> {
let mut text = Text::default();
let columns = self.columns();
let cell_total = width.saturating_sub(columns).saturating_sub(1);
@@ -167,7 +175,7 @@ impl Table {
if let Some(caption) = &self.caption {
let subw = width.saturating_sub(6);
let mut printer =
TextPrinter::new(subw, style, true, emoji_shortcodes).align(Alignment::Center);
TextPrinter::new(subw, style, true, settings).align(Alignment::Center);
caption.print(&mut printer, style);
for mut line in printer.finish().lines {
@@ -214,7 +222,7 @@ impl Table {
CellType::Data => style,
};
cell.to_text(*w, style, emoji_shortcodes)
cell.to_text(*w, style, settings)
} else {
space_text(*w, style)
};
@@ -271,13 +279,21 @@ pub enum StyleTreeNode {
Ruler,
Style(Box<StyleTreeNode>, Style),
Table(Table),
Text(String),
Text(Cow<'static, str>),
Sequence(StyleTreeChildren),
RoomAlias(OwnedRoomAliasId),
RoomId(OwnedRoomId),
UserId(OwnedUserId),
}
impl StyleTreeNode {
pub fn to_text(&self, width: usize, style: Style, emoji_shortcodes: bool) -> Text {
let mut printer = TextPrinter::new(width, style, true, emoji_shortcodes);
pub fn to_text<'a>(
&'a self,
width: usize,
style: Style,
settings: &'a ApplicationSettings,
) -> Text<'a> {
let mut printer = TextPrinter::new(width, style, true, settings);
self.print(&mut printer, style);
printer.finish()
}
@@ -312,6 +328,11 @@ impl StyleTreeNode {
StyleTreeNode::Ruler => {},
StyleTreeNode::Text(_) => {},
StyleTreeNode::Break => {},
// TODO: eventually these should turn into internal links:
StyleTreeNode::UserId(_) => {},
StyleTreeNode::RoomId(_) => {},
StyleTreeNode::RoomAlias(_) => {},
}
}
@@ -430,14 +451,14 @@ impl StyleTreeNode {
}
},
StyleTreeNode::Table(table) => {
let text = table.to_text(width, style, printer.emoji_shortcodes());
let text = table.to_text(width, style, printer.settings);
printer.push_text(text);
},
StyleTreeNode::Break => {
printer.push_break();
},
StyleTreeNode::Text(s) => {
printer.push_str(s.as_str(), style);
printer.push_str(s.as_ref(), style);
},
StyleTreeNode::Style(child, patch) => child.print(printer, style.patch(*patch)),
@@ -446,13 +467,26 @@ impl StyleTreeNode {
child.print(printer, style);
}
},
StyleTreeNode::UserId(user_id) => {
let style = printer.settings().get_user_style(user_id);
printer.push_str(user_id.as_str(), style);
},
StyleTreeNode::RoomId(room_id) => {
let bold = style.add_modifier(StyleModifier::BOLD);
printer.push_str(room_id.as_str(), bold);
},
StyleTreeNode::RoomAlias(alias) => {
let bold = style.add_modifier(StyleModifier::BOLD);
printer.push_str(alias.as_str(), bold);
},
}
}
}
/// A processed HTML document.
pub struct StyleTree {
children: StyleTreeChildren,
pub(super) children: StyleTreeChildren,
}
impl StyleTree {
@@ -466,14 +500,14 @@ impl StyleTree {
return links;
}
pub fn to_text(
&self,
pub fn to_text<'a>(
&'a self,
width: usize,
style: Style,
hide_reply: bool,
emoji_shortcodes: bool,
) -> Text<'_> {
let mut printer = TextPrinter::new(width, style, hide_reply, emoji_shortcodes);
settings: &'a ApplicationSettings,
) -> Text<'a> {
let mut printer = TextPrinter::new(width, style, hide_reply, settings);
for child in self.children.iter() {
child.print(&mut printer, style);
@@ -661,7 +695,7 @@ fn h2t(hdl: &Handle, state: &mut TreeGenState) -> StyleTreeChildren {
let tree = match &node.data {
NodeData::Document => *c2t(node.children.borrow().as_slice(), state),
NodeData::Text { contents } => StyleTreeNode::Text(contents.borrow().to_string()),
NodeData::Text { contents } => StyleTreeNode::Text(contents.borrow().to_string().into()),
NodeData::Element { name, attrs, .. } => {
match name.local.as_ref() {
// Message that this one replies to.
@@ -811,17 +845,19 @@ pub fn parse_matrix_html(s: &str) -> StyleTree {
#[cfg(test)]
pub mod tests {
use super::*;
use crate::tests::mock_settings;
use crate::util::space_span;
use pretty_assertions::assert_eq;
use unicode_width::UnicodeWidthStr;
#[test]
fn test_header() {
let settings = mock_settings();
let bold = Style::default().add_modifier(StyleModifier::BOLD);
let s = "<h1>Header 1</h1>";
let tree = parse_matrix_html(s);
let text = tree.to_text(20, Style::default(), false, false);
let text = tree.to_text(20, Style::default(), false, &settings);
assert_eq!(text.lines, vec![Line::from(vec![
Span::styled("#", bold),
Span::styled(" ", bold),
@@ -833,7 +869,7 @@ pub mod tests {
let s = "<h2>Header 2</h2>";
let tree = parse_matrix_html(s);
let text = tree.to_text(20, Style::default(), false, false);
let text = tree.to_text(20, Style::default(), false, &settings);
assert_eq!(text.lines, vec![Line::from(vec![
Span::styled("#", bold),
Span::styled("#", bold),
@@ -846,7 +882,7 @@ pub mod tests {
let s = "<h3>Header 3</h3>";
let tree = parse_matrix_html(s);
let text = tree.to_text(20, Style::default(), false, false);
let text = tree.to_text(20, Style::default(), false, &settings);
assert_eq!(text.lines, vec![Line::from(vec![
Span::styled("#", bold),
Span::styled("#", bold),
@@ -860,7 +896,7 @@ pub mod tests {
let s = "<h4>Header 4</h4>";
let tree = parse_matrix_html(s);
let text = tree.to_text(20, Style::default(), false, false);
let text = tree.to_text(20, Style::default(), false, &settings);
assert_eq!(text.lines, vec![Line::from(vec![
Span::styled("#", bold),
Span::styled("#", bold),
@@ -875,7 +911,7 @@ pub mod tests {
let s = "<h5>Header 5</h5>";
let tree = parse_matrix_html(s);
let text = tree.to_text(20, Style::default(), false, false);
let text = tree.to_text(20, Style::default(), false, &settings);
assert_eq!(text.lines, vec![Line::from(vec![
Span::styled("#", bold),
Span::styled("#", bold),
@@ -891,7 +927,7 @@ pub mod tests {
let s = "<h6>Header 6</h6>";
let tree = parse_matrix_html(s);
let text = tree.to_text(20, Style::default(), false, false);
let text = tree.to_text(20, Style::default(), false, &settings);
assert_eq!(text.lines, vec![Line::from(vec![
Span::styled("#", bold),
Span::styled("#", bold),
@@ -909,6 +945,7 @@ pub mod tests {
#[test]
fn test_style() {
let settings = mock_settings();
let def = Style::default();
let bold = def.add_modifier(StyleModifier::BOLD);
let italic = def.add_modifier(StyleModifier::ITALIC);
@@ -918,7 +955,7 @@ pub mod tests {
let s = "<b>Bold!</b>";
let tree = parse_matrix_html(s);
let text = tree.to_text(20, Style::default(), false, false);
let text = tree.to_text(20, Style::default(), false, &settings);
assert_eq!(text.lines, vec![Line::from(vec![
Span::styled("Bold", bold),
Span::styled("!", bold),
@@ -927,7 +964,7 @@ pub mod tests {
let s = "<strong>Bold!</strong>";
let tree = parse_matrix_html(s);
let text = tree.to_text(20, Style::default(), false, false);
let text = tree.to_text(20, Style::default(), false, &settings);
assert_eq!(text.lines, vec![Line::from(vec![
Span::styled("Bold", bold),
Span::styled("!", bold),
@@ -936,7 +973,7 @@ pub mod tests {
let s = "<i>Italic!</i>";
let tree = parse_matrix_html(s);
let text = tree.to_text(20, Style::default(), false, false);
let text = tree.to_text(20, Style::default(), false, &settings);
assert_eq!(text.lines, vec![Line::from(vec![
Span::styled("Italic", italic),
Span::styled("!", italic),
@@ -945,7 +982,7 @@ pub mod tests {
let s = "<em>Italic!</em>";
let tree = parse_matrix_html(s);
let text = tree.to_text(20, Style::default(), false, false);
let text = tree.to_text(20, Style::default(), false, &settings);
assert_eq!(text.lines, vec![Line::from(vec![
Span::styled("Italic", italic),
Span::styled("!", italic),
@@ -954,7 +991,7 @@ pub mod tests {
let s = "<del>Strikethrough!</del>";
let tree = parse_matrix_html(s);
let text = tree.to_text(20, Style::default(), false, false);
let text = tree.to_text(20, Style::default(), false, &settings);
assert_eq!(text.lines, vec![Line::from(vec![
Span::styled("Strikethrough", strike),
Span::styled("!", strike),
@@ -963,7 +1000,7 @@ pub mod tests {
let s = "<strike>Strikethrough!</strike>";
let tree = parse_matrix_html(s);
let text = tree.to_text(20, Style::default(), false, false);
let text = tree.to_text(20, Style::default(), false, &settings);
assert_eq!(text.lines, vec![Line::from(vec![
Span::styled("Strikethrough", strike),
Span::styled("!", strike),
@@ -972,7 +1009,7 @@ pub mod tests {
let s = "<u>Underline!</u>";
let tree = parse_matrix_html(s);
let text = tree.to_text(20, Style::default(), false, false);
let text = tree.to_text(20, Style::default(), false, &settings);
assert_eq!(text.lines, vec![Line::from(vec![
Span::styled("Underline", underl),
Span::styled("!", underl),
@@ -981,7 +1018,7 @@ pub mod tests {
let s = "<font color=\"#ff0000\">Red!</u>";
let tree = parse_matrix_html(s);
let text = tree.to_text(20, Style::default(), false, false);
let text = tree.to_text(20, Style::default(), false, &settings);
assert_eq!(text.lines, vec![Line::from(vec![
Span::styled("Red", red),
Span::styled("!", red),
@@ -990,7 +1027,7 @@ pub mod tests {
let s = "<font color=\"red\">Red!</u>";
let tree = parse_matrix_html(s);
let text = tree.to_text(20, Style::default(), false, false);
let text = tree.to_text(20, Style::default(), false, &settings);
assert_eq!(text.lines, vec![Line::from(vec![
Span::styled("Red", red),
Span::styled("!", red),
@@ -1000,9 +1037,10 @@ pub mod tests {
#[test]
fn test_paragraph() {
let settings = mock_settings();
let s = "<p>Hello world!</p><p>Content</p><p>Goodbye world!</p>";
let tree = parse_matrix_html(s);
let text = tree.to_text(10, Style::default(), false, false);
let text = tree.to_text(10, Style::default(), false, &settings);
assert_eq!(text.lines.len(), 7);
assert_eq!(
text.lines[0],
@@ -1027,9 +1065,10 @@ pub mod tests {
#[test]
fn test_blockquote() {
let settings = mock_settings();
let s = "<blockquote>Hello world!</blockquote>";
let tree = parse_matrix_html(s);
let text = tree.to_text(10, Style::default(), false, false);
let text = tree.to_text(10, Style::default(), false, &settings);
assert_eq!(text.lines.len(), 2);
assert_eq!(
text.lines[0],
@@ -1043,9 +1082,10 @@ pub mod tests {
#[test]
fn test_list_unordered() {
let settings = mock_settings();
let s = "<ul><li>List Item 1</li><li>List Item 2</li><li>List Item 3</li></ul>";
let tree = parse_matrix_html(s);
let text = tree.to_text(8, Style::default(), false, false);
let text = tree.to_text(8, Style::default(), false, &settings);
assert_eq!(text.lines.len(), 6);
assert_eq!(
text.lines[0],
@@ -1105,9 +1145,10 @@ pub mod tests {
#[test]
fn test_list_ordered() {
let settings = mock_settings();
let s = "<ol><li>List Item 1</li><li>List Item 2</li><li>List Item 3</li></ol>";
let tree = parse_matrix_html(s);
let text = tree.to_text(9, Style::default(), false, false);
let text = tree.to_text(9, Style::default(), false, &settings);
assert_eq!(text.lines.len(), 6);
assert_eq!(
text.lines[0],
@@ -1167,6 +1208,7 @@ pub mod tests {
#[test]
fn test_table() {
let settings = mock_settings();
let s = "<table>\
<thead>\
<tr><th>Column 1</th><th>Column 2</th><th>Column 3</th></tr>
@@ -1177,7 +1219,7 @@ pub mod tests {
<tr><td>a</td><td>b</td><td>c</td></tr>\
</tbody></table>";
let tree = parse_matrix_html(s);
let text = tree.to_text(15, Style::default(), false, false);
let text = tree.to_text(15, Style::default(), false, &settings);
let bold = Style::default().add_modifier(StyleModifier::BOLD);
assert_eq!(text.lines.len(), 11);
@@ -1267,10 +1309,11 @@ pub mod tests {
#[test]
fn test_matrix_reply() {
let settings = mock_settings();
let s = "<mx-reply>This was replied to</mx-reply>This is the reply";
let tree = parse_matrix_html(s);
let text = tree.to_text(10, Style::default(), false, false);
let text = tree.to_text(10, Style::default(), false, &settings);
assert_eq!(text.lines.len(), 4);
assert_eq!(
text.lines[0],
@@ -1307,7 +1350,7 @@ pub mod tests {
);
let tree = parse_matrix_html(s);
let text = tree.to_text(10, Style::default(), true, false);
let text = tree.to_text(10, Style::default(), true, &settings);
assert_eq!(text.lines.len(), 2);
assert_eq!(
text.lines[0],
@@ -1332,9 +1375,10 @@ pub mod tests {
#[test]
fn test_self_closing() {
let settings = mock_settings();
let s = "Hello<br>World<br>Goodbye";
let tree = parse_matrix_html(s);
let text = tree.to_text(7, Style::default(), true, false);
let text = tree.to_text(7, Style::default(), true, &settings);
assert_eq!(text.lines.len(), 3);
assert_eq!(text.lines[0], Line::from(vec![Span::raw("Hello"), Span::raw(" "),]));
assert_eq!(text.lines[1], Line::from(vec![Span::raw("World"), Span::raw(" "),]));
@@ -1343,9 +1387,10 @@ pub mod tests {
#[test]
fn test_embedded_newline() {
let settings = mock_settings();
let s = "<p>Hello\nWorld</p>";
let tree = parse_matrix_html(s);
let text = tree.to_text(15, Style::default(), true, false);
let text = tree.to_text(15, Style::default(), true, &settings);
assert_eq!(text.lines.len(), 1);
assert_eq!(
text.lines[0],
@@ -1360,6 +1405,7 @@ pub mod tests {
#[test]
fn test_pre_tag() {
let settings = mock_settings();
let s = concat!(
"<pre><code class=\"language-rust\">",
"fn hello() -&gt; usize {\n",
@@ -1368,7 +1414,7 @@ pub mod tests {
"</code></pre>\n"
);
let tree = parse_matrix_html(s);
let text = tree.to_text(25, Style::default(), true, false);
let text = tree.to_text(25, Style::default(), true, &settings);
assert_eq!(text.lines.len(), 5);
assert_eq!(
text.lines[0],
@@ -1432,6 +1478,11 @@ pub mod tests {
#[test]
fn test_emoji_shortcodes() {
let mut enabled = mock_settings();
enabled.tunables.message_shortcode_display = true;
let mut disabled = mock_settings();
disabled.tunables.message_shortcode_display = false;
for shortcode in ["exploding_head", "polar_bear", "canada"] {
let emoji = emojis::get_by_shortcode(shortcode).unwrap().as_str();
let emoji_width = UnicodeWidthStr::width(emoji);
@@ -1440,13 +1491,13 @@ pub mod tests {
let s = format!("<p>{emoji}</p>");
let tree = parse_matrix_html(s.as_str());
// Test with emojis_shortcodes set to false
let text = tree.to_text(20, Style::default(), false, false);
let text = tree.to_text(20, Style::default(), false, &disabled);
assert_eq!(text.lines, vec![Line::from(vec![
Span::raw(emoji),
space_span(20 - emoji_width, Style::default()),
]),]);
// Test with emojis_shortcodes set to true
let text = tree.to_text(20, Style::default(), false, true);
let text = tree.to_text(20, Style::default(), false, &enabled);
assert_eq!(text.lines, vec![Line::from(vec![
Span::raw(replacement.as_str()),
space_span(20 - replacement_width, Style::default()),