mirror of
https://github.com/youwen5/iamb.git
synced 2025-06-19 21:29:52 -07:00
Support displaying shortcodes instead of Emojis in messages (#222)
This commit is contained in:
parent
0c52375e06
commit
23a729e565
8 changed files with 164 additions and 49 deletions
|
@ -10,6 +10,7 @@
|
|||
},
|
||||
"settings": {
|
||||
"log_level": "warn",
|
||||
"message_shortcode_display": false,
|
||||
"reaction_display": true,
|
||||
"reaction_shortcode_display": false,
|
||||
"read_receipt_send": true,
|
||||
|
|
|
@ -53,6 +53,10 @@ overridden as described in *PROFILES*.
|
|||
> Specifies the lowest log level that should be shown. Possible values
|
||||
> are: _trace_, _debug_, _info_, _warn_, and _error_.
|
||||
|
||||
**message_shortcode_display** (type: boolean)
|
||||
> Defines whether or not emoji characters in messages should be replaced by
|
||||
> their respective shortcodes.
|
||||
|
||||
**reaction_display** (type: boolean)
|
||||
> Defines whether or not reactions should be shown.
|
||||
|
||||
|
|
|
@ -469,6 +469,7 @@ impl SortOverrides {
|
|||
#[derive(Clone)]
|
||||
pub struct TunableValues {
|
||||
pub log_level: Level,
|
||||
pub message_shortcode_display: bool,
|
||||
pub reaction_display: bool,
|
||||
pub reaction_shortcode_display: bool,
|
||||
pub read_receipt_send: bool,
|
||||
|
@ -489,6 +490,7 @@ pub struct TunableValues {
|
|||
#[derive(Clone, Default, Deserialize)]
|
||||
pub struct Tunables {
|
||||
pub log_level: Option<LogLevel>,
|
||||
pub message_shortcode_display: Option<bool>,
|
||||
pub reaction_display: Option<bool>,
|
||||
pub reaction_shortcode_display: Option<bool>,
|
||||
pub read_receipt_send: Option<bool>,
|
||||
|
@ -511,6 +513,9 @@ impl Tunables {
|
|||
fn merge(self, other: Self) -> Self {
|
||||
Tunables {
|
||||
log_level: self.log_level.or(other.log_level),
|
||||
message_shortcode_display: self
|
||||
.message_shortcode_display
|
||||
.or(other.message_shortcode_display),
|
||||
reaction_display: self.reaction_display.or(other.reaction_display),
|
||||
reaction_shortcode_display: self
|
||||
.reaction_shortcode_display
|
||||
|
@ -534,6 +539,7 @@ impl Tunables {
|
|||
fn values(self) -> TunableValues {
|
||||
TunableValues {
|
||||
log_level: self.log_level.map(Level::from).unwrap_or(Level::INFO),
|
||||
message_shortcode_display: self.message_shortcode_display.unwrap_or(false),
|
||||
reaction_display: self.reaction_display.unwrap_or(true),
|
||||
reaction_shortcode_display: self.reaction_shortcode_display.unwrap_or(false),
|
||||
read_receipt_send: self.read_receipt_send.unwrap_or(true),
|
||||
|
|
|
@ -148,7 +148,7 @@ impl Table {
|
|||
}
|
||||
}
|
||||
|
||||
fn to_text(&self, width: usize, style: Style) -> Text {
|
||||
fn to_text(&self, width: usize, style: Style, emoji_shortcodes: bool) -> Text {
|
||||
let mut text = Text::default();
|
||||
let columns = self.columns();
|
||||
let cell_total = width.saturating_sub(columns).saturating_sub(1);
|
||||
|
@ -166,7 +166,8 @@ impl Table {
|
|||
|
||||
if let Some(caption) = &self.caption {
|
||||
let subw = width.saturating_sub(6);
|
||||
let mut printer = TextPrinter::new(subw, style, true).align(Alignment::Center);
|
||||
let mut printer =
|
||||
TextPrinter::new(subw, style, true, emoji_shortcodes).align(Alignment::Center);
|
||||
caption.print(&mut printer, style);
|
||||
|
||||
for mut line in printer.finish().lines {
|
||||
|
@ -213,7 +214,7 @@ impl Table {
|
|||
CellType::Data => style,
|
||||
};
|
||||
|
||||
cell.to_text(*w, style)
|
||||
cell.to_text(*w, style, emoji_shortcodes)
|
||||
} else {
|
||||
space_text(*w, style)
|
||||
};
|
||||
|
@ -274,8 +275,8 @@ pub enum StyleTreeNode {
|
|||
}
|
||||
|
||||
impl StyleTreeNode {
|
||||
pub fn to_text(&self, width: usize, style: Style) -> Text {
|
||||
let mut printer = TextPrinter::new(width, style, true);
|
||||
pub fn to_text(&self, width: usize, style: Style, emoji_shortcodes: bool) -> Text {
|
||||
let mut printer = TextPrinter::new(width, style, true, emoji_shortcodes);
|
||||
self.print(&mut printer, style);
|
||||
printer.finish()
|
||||
}
|
||||
|
@ -428,7 +429,7 @@ impl StyleTreeNode {
|
|||
}
|
||||
},
|
||||
StyleTreeNode::Table(table) => {
|
||||
let text = table.to_text(width, style);
|
||||
let text = table.to_text(width, style, printer.emoji_shortcodes());
|
||||
printer.push_text(text);
|
||||
},
|
||||
StyleTreeNode::Break => {
|
||||
|
@ -464,8 +465,14 @@ impl StyleTree {
|
|||
return links;
|
||||
}
|
||||
|
||||
pub fn to_text(&self, width: usize, style: Style, hide_reply: bool) -> Text<'_> {
|
||||
let mut printer = TextPrinter::new(width, style, hide_reply);
|
||||
pub fn to_text(
|
||||
&self,
|
||||
width: usize,
|
||||
style: Style,
|
||||
hide_reply: bool,
|
||||
emoji_shortcodes: bool,
|
||||
) -> Text<'_> {
|
||||
let mut printer = TextPrinter::new(width, style, hide_reply, emoji_shortcodes);
|
||||
|
||||
for child in self.children.iter() {
|
||||
child.print(&mut printer, style);
|
||||
|
@ -805,6 +812,7 @@ pub mod tests {
|
|||
use super::*;
|
||||
use crate::util::space_span;
|
||||
use pretty_assertions::assert_eq;
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
|
||||
#[test]
|
||||
fn test_header() {
|
||||
|
@ -812,7 +820,7 @@ pub mod tests {
|
|||
|
||||
let s = "<h1>Header 1</h1>";
|
||||
let tree = parse_matrix_html(s);
|
||||
let text = tree.to_text(20, Style::default(), false);
|
||||
let text = tree.to_text(20, Style::default(), false, false);
|
||||
assert_eq!(text.lines, vec![Line::from(vec![
|
||||
Span::styled("#", bold),
|
||||
Span::styled(" ", bold),
|
||||
|
@ -824,7 +832,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);
|
||||
let text = tree.to_text(20, Style::default(), false, false);
|
||||
assert_eq!(text.lines, vec![Line::from(vec![
|
||||
Span::styled("#", bold),
|
||||
Span::styled("#", bold),
|
||||
|
@ -837,7 +845,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);
|
||||
let text = tree.to_text(20, Style::default(), false, false);
|
||||
assert_eq!(text.lines, vec![Line::from(vec![
|
||||
Span::styled("#", bold),
|
||||
Span::styled("#", bold),
|
||||
|
@ -851,7 +859,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);
|
||||
let text = tree.to_text(20, Style::default(), false, false);
|
||||
assert_eq!(text.lines, vec![Line::from(vec![
|
||||
Span::styled("#", bold),
|
||||
Span::styled("#", bold),
|
||||
|
@ -866,7 +874,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);
|
||||
let text = tree.to_text(20, Style::default(), false, false);
|
||||
assert_eq!(text.lines, vec![Line::from(vec![
|
||||
Span::styled("#", bold),
|
||||
Span::styled("#", bold),
|
||||
|
@ -882,7 +890,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);
|
||||
let text = tree.to_text(20, Style::default(), false, false);
|
||||
assert_eq!(text.lines, vec![Line::from(vec![
|
||||
Span::styled("#", bold),
|
||||
Span::styled("#", bold),
|
||||
|
@ -909,7 +917,7 @@ pub mod tests {
|
|||
|
||||
let s = "<b>Bold!</b>";
|
||||
let tree = parse_matrix_html(s);
|
||||
let text = tree.to_text(20, Style::default(), false);
|
||||
let text = tree.to_text(20, Style::default(), false, false);
|
||||
assert_eq!(text.lines, vec![Line::from(vec![
|
||||
Span::styled("Bold", bold),
|
||||
Span::styled("!", bold),
|
||||
|
@ -918,7 +926,7 @@ pub mod tests {
|
|||
|
||||
let s = "<strong>Bold!</strong>";
|
||||
let tree = parse_matrix_html(s);
|
||||
let text = tree.to_text(20, Style::default(), false);
|
||||
let text = tree.to_text(20, Style::default(), false, false);
|
||||
assert_eq!(text.lines, vec![Line::from(vec![
|
||||
Span::styled("Bold", bold),
|
||||
Span::styled("!", bold),
|
||||
|
@ -927,7 +935,7 @@ pub mod tests {
|
|||
|
||||
let s = "<i>Italic!</i>";
|
||||
let tree = parse_matrix_html(s);
|
||||
let text = tree.to_text(20, Style::default(), false);
|
||||
let text = tree.to_text(20, Style::default(), false, false);
|
||||
assert_eq!(text.lines, vec![Line::from(vec![
|
||||
Span::styled("Italic", italic),
|
||||
Span::styled("!", italic),
|
||||
|
@ -936,7 +944,7 @@ pub mod tests {
|
|||
|
||||
let s = "<em>Italic!</em>";
|
||||
let tree = parse_matrix_html(s);
|
||||
let text = tree.to_text(20, Style::default(), false);
|
||||
let text = tree.to_text(20, Style::default(), false, false);
|
||||
assert_eq!(text.lines, vec![Line::from(vec![
|
||||
Span::styled("Italic", italic),
|
||||
Span::styled("!", italic),
|
||||
|
@ -945,7 +953,7 @@ pub mod tests {
|
|||
|
||||
let s = "<del>Strikethrough!</del>";
|
||||
let tree = parse_matrix_html(s);
|
||||
let text = tree.to_text(20, Style::default(), false);
|
||||
let text = tree.to_text(20, Style::default(), false, false);
|
||||
assert_eq!(text.lines, vec![Line::from(vec![
|
||||
Span::styled("Strikethrough", strike),
|
||||
Span::styled("!", strike),
|
||||
|
@ -954,7 +962,7 @@ pub mod tests {
|
|||
|
||||
let s = "<strike>Strikethrough!</strike>";
|
||||
let tree = parse_matrix_html(s);
|
||||
let text = tree.to_text(20, Style::default(), false);
|
||||
let text = tree.to_text(20, Style::default(), false, false);
|
||||
assert_eq!(text.lines, vec![Line::from(vec![
|
||||
Span::styled("Strikethrough", strike),
|
||||
Span::styled("!", strike),
|
||||
|
@ -963,7 +971,7 @@ pub mod tests {
|
|||
|
||||
let s = "<u>Underline!</u>";
|
||||
let tree = parse_matrix_html(s);
|
||||
let text = tree.to_text(20, Style::default(), false);
|
||||
let text = tree.to_text(20, Style::default(), false, false);
|
||||
assert_eq!(text.lines, vec![Line::from(vec![
|
||||
Span::styled("Underline", underl),
|
||||
Span::styled("!", underl),
|
||||
|
@ -972,7 +980,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);
|
||||
let text = tree.to_text(20, Style::default(), false, false);
|
||||
assert_eq!(text.lines, vec![Line::from(vec![
|
||||
Span::styled("Red", red),
|
||||
Span::styled("!", red),
|
||||
|
@ -981,7 +989,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);
|
||||
let text = tree.to_text(20, Style::default(), false, false);
|
||||
assert_eq!(text.lines, vec![Line::from(vec![
|
||||
Span::styled("Red", red),
|
||||
Span::styled("!", red),
|
||||
|
@ -993,7 +1001,7 @@ pub mod tests {
|
|||
fn test_paragraph() {
|
||||
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);
|
||||
let text = tree.to_text(10, Style::default(), false, false);
|
||||
assert_eq!(text.lines.len(), 7);
|
||||
assert_eq!(
|
||||
text.lines[0],
|
||||
|
@ -1020,7 +1028,7 @@ pub mod tests {
|
|||
fn test_blockquote() {
|
||||
let s = "<blockquote>Hello world!</blockquote>";
|
||||
let tree = parse_matrix_html(s);
|
||||
let text = tree.to_text(10, Style::default(), false);
|
||||
let text = tree.to_text(10, Style::default(), false, false);
|
||||
assert_eq!(text.lines.len(), 2);
|
||||
assert_eq!(
|
||||
text.lines[0],
|
||||
|
@ -1036,7 +1044,7 @@ pub mod tests {
|
|||
fn test_list_unordered() {
|
||||
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);
|
||||
let text = tree.to_text(8, Style::default(), false, false);
|
||||
assert_eq!(text.lines.len(), 6);
|
||||
assert_eq!(
|
||||
text.lines[0],
|
||||
|
@ -1098,7 +1106,7 @@ pub mod tests {
|
|||
fn test_list_ordered() {
|
||||
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);
|
||||
let text = tree.to_text(9, Style::default(), false, false);
|
||||
assert_eq!(text.lines.len(), 6);
|
||||
assert_eq!(
|
||||
text.lines[0],
|
||||
|
@ -1168,7 +1176,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);
|
||||
let text = tree.to_text(15, Style::default(), false, false);
|
||||
let bold = Style::default().add_modifier(StyleModifier::BOLD);
|
||||
assert_eq!(text.lines.len(), 11);
|
||||
|
||||
|
@ -1261,7 +1269,7 @@ pub mod tests {
|
|||
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);
|
||||
let text = tree.to_text(10, Style::default(), false, false);
|
||||
assert_eq!(text.lines.len(), 4);
|
||||
assert_eq!(
|
||||
text.lines[0],
|
||||
|
@ -1298,7 +1306,7 @@ pub mod tests {
|
|||
);
|
||||
|
||||
let tree = parse_matrix_html(s);
|
||||
let text = tree.to_text(10, Style::default(), true);
|
||||
let text = tree.to_text(10, Style::default(), true, false);
|
||||
assert_eq!(text.lines.len(), 2);
|
||||
assert_eq!(
|
||||
text.lines[0],
|
||||
|
@ -1325,7 +1333,7 @@ pub mod tests {
|
|||
fn test_self_closing() {
|
||||
let s = "Hello<br>World<br>Goodbye";
|
||||
let tree = parse_matrix_html(s);
|
||||
let text = tree.to_text(7, Style::default(), true);
|
||||
let text = tree.to_text(7, Style::default(), true, false);
|
||||
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(" "),]));
|
||||
|
@ -1336,7 +1344,7 @@ pub mod tests {
|
|||
fn test_embedded_newline() {
|
||||
let s = "<p>Hello\nWorld</p>";
|
||||
let tree = parse_matrix_html(s);
|
||||
let text = tree.to_text(15, Style::default(), true);
|
||||
let text = tree.to_text(15, Style::default(), true, false);
|
||||
assert_eq!(text.lines.len(), 1);
|
||||
assert_eq!(
|
||||
text.lines[0],
|
||||
|
@ -1359,7 +1367,7 @@ pub mod tests {
|
|||
"</code></pre>\n"
|
||||
);
|
||||
let tree = parse_matrix_html(s);
|
||||
let text = tree.to_text(25, Style::default(), true);
|
||||
let text = tree.to_text(25, Style::default(), true, false);
|
||||
assert_eq!(text.lines.len(), 5);
|
||||
assert_eq!(
|
||||
text.lines[0],
|
||||
|
@ -1420,4 +1428,28 @@ pub mod tests {
|
|||
])
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_emoji_shortcodes() {
|
||||
for shortcode in ["exploding_head", "polar_bear", "canada"] {
|
||||
let emoji = emojis::get_by_shortcode(shortcode).unwrap().as_str();
|
||||
let emoji_width = UnicodeWidthStr::width(emoji);
|
||||
let replacement = format!(":{shortcode}:");
|
||||
let replacement_width = UnicodeWidthStr::width(replacement.as_str());
|
||||
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);
|
||||
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);
|
||||
assert_eq!(text.lines, vec![Line::from(vec![
|
||||
Span::raw(replacement.as_str()),
|
||||
space_span(20 - replacement_width, Style::default()),
|
||||
])]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -61,7 +61,7 @@ use crate::{
|
|||
base::RoomInfo,
|
||||
config::ApplicationSettings,
|
||||
message::html::{parse_matrix_html, StyleTree},
|
||||
util::{space, space_span, take_width, wrapped_text},
|
||||
util::{replace_emojis_in_str, space, space_span, take_width, wrapped_text},
|
||||
};
|
||||
|
||||
mod html;
|
||||
|
@ -834,7 +834,8 @@ impl Message {
|
|||
|
||||
if let Some(r) = &reply {
|
||||
let w = width.saturating_sub(2);
|
||||
let mut replied = r.show_msg(w, style, true);
|
||||
let mut replied =
|
||||
r.show_msg(w, style, true, settings.tunables.message_shortcode_display);
|
||||
let mut sender = r.sender_span(info, settings);
|
||||
let sender_width = UnicodeWidthStr::width(sender.content.as_ref());
|
||||
let trailing = w.saturating_sub(sender_width + 1);
|
||||
|
@ -862,7 +863,12 @@ impl Message {
|
|||
}
|
||||
|
||||
// Now show the message contents, and the inlined reply if we couldn't find it above.
|
||||
let msg = self.show_msg(width, style, reply.is_some());
|
||||
let msg = self.show_msg(
|
||||
width,
|
||||
style,
|
||||
reply.is_some(),
|
||||
settings.tunables.message_shortcode_display,
|
||||
);
|
||||
fmt.push_text(msg, style, &mut text);
|
||||
|
||||
if text.lines.is_empty() {
|
||||
|
@ -871,7 +877,9 @@ impl Message {
|
|||
}
|
||||
|
||||
if settings.tunables.reaction_display {
|
||||
let mut emojis = printer::TextPrinter::new(width, style, false);
|
||||
// Pass false for emoji_shortcodes parameter because we handle shortcodes ourselves
|
||||
// before pushing to the printer.
|
||||
let mut emojis = printer::TextPrinter::new(width, style, false, false);
|
||||
let mut reactions = 0;
|
||||
|
||||
for (key, count) in info.get_reactions(self.event.event_id()).into_iter() {
|
||||
|
@ -917,7 +925,9 @@ impl Message {
|
|||
|
||||
if len > 0 {
|
||||
let style = Style::default();
|
||||
let mut threaded = printer::TextPrinter::new(width, style, false).literal(true);
|
||||
let emoji_shortcodes = settings.tunables.message_shortcode_display;
|
||||
let mut threaded =
|
||||
printer::TextPrinter::new(width, style, false, emoji_shortcodes).literal(true);
|
||||
let len = Span::styled(len.to_string(), style.add_modifier(StyleModifier::BOLD));
|
||||
threaded.push_str(" \u{2937} ", style);
|
||||
threaded.push_span_nobreak(len);
|
||||
|
@ -929,11 +939,20 @@ impl Message {
|
|||
text
|
||||
}
|
||||
|
||||
pub fn show_msg(&self, width: usize, style: Style, hide_reply: bool) -> Text {
|
||||
pub fn show_msg(
|
||||
&self,
|
||||
width: usize,
|
||||
style: Style,
|
||||
hide_reply: bool,
|
||||
emoji_shortcodes: bool,
|
||||
) -> Text {
|
||||
if let Some(html) = &self.html {
|
||||
html.to_text(width, style, hide_reply)
|
||||
html.to_text(width, style, hide_reply, emoji_shortcodes)
|
||||
} else {
|
||||
let mut msg = self.event.body();
|
||||
if emoji_shortcodes {
|
||||
msg = Cow::Owned(replace_emojis_in_str(msg.as_ref()));
|
||||
}
|
||||
|
||||
if self.downloaded {
|
||||
msg.to_mut().push_str(" \u{2705}");
|
||||
|
|
|
@ -11,7 +11,13 @@ use ratatui::text::{Line, Span, Text};
|
|||
use unicode_segmentation::UnicodeSegmentation;
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
|
||||
use crate::util::{space_span, take_width};
|
||||
use crate::util::{
|
||||
replace_emojis_in_line,
|
||||
replace_emojis_in_span,
|
||||
replace_emojis_in_str,
|
||||
space_span,
|
||||
take_width,
|
||||
};
|
||||
|
||||
/// Wrap styled text for the current terminal width.
|
||||
pub struct TextPrinter<'a> {
|
||||
|
@ -19,6 +25,7 @@ pub struct TextPrinter<'a> {
|
|||
width: usize,
|
||||
base_style: Style,
|
||||
hide_reply: bool,
|
||||
emoji_shortcodes: bool,
|
||||
|
||||
alignment: Alignment,
|
||||
curr_spans: Vec<Span<'a>>,
|
||||
|
@ -28,12 +35,13 @@ pub struct TextPrinter<'a> {
|
|||
|
||||
impl<'a> TextPrinter<'a> {
|
||||
/// Create a new printer.
|
||||
pub fn new(width: usize, base_style: Style, hide_reply: bool) -> Self {
|
||||
pub fn new(width: usize, base_style: Style, hide_reply: bool, emoji_shortcodes: bool) -> Self {
|
||||
TextPrinter {
|
||||
text: Text::default(),
|
||||
width,
|
||||
base_style,
|
||||
hide_reply,
|
||||
emoji_shortcodes,
|
||||
|
||||
alignment: Alignment::Left,
|
||||
curr_spans: vec![],
|
||||
|
@ -59,6 +67,11 @@ impl<'a> TextPrinter<'a> {
|
|||
self.hide_reply
|
||||
}
|
||||
|
||||
/// Indicates whether emojis should be replaced by shortcodes
|
||||
pub fn emoji_shortcodes(&self) -> bool {
|
||||
self.emoji_shortcodes
|
||||
}
|
||||
|
||||
/// Indicates the current printer's width.
|
||||
pub fn width(&self) -> usize {
|
||||
self.width
|
||||
|
@ -71,6 +84,7 @@ impl<'a> TextPrinter<'a> {
|
|||
width: self.width.saturating_sub(indent),
|
||||
base_style: self.base_style,
|
||||
hide_reply: self.hide_reply,
|
||||
emoji_shortcodes: self.emoji_shortcodes,
|
||||
|
||||
alignment: self.alignment,
|
||||
curr_spans: vec![],
|
||||
|
@ -164,7 +178,10 @@ impl<'a> TextPrinter<'a> {
|
|||
}
|
||||
|
||||
/// Push a [Span] that isn't allowed to break across lines.
|
||||
pub fn push_span_nobreak(&mut self, span: Span<'a>) {
|
||||
pub fn push_span_nobreak(&mut self, mut span: Span<'a>) {
|
||||
if self.emoji_shortcodes {
|
||||
replace_emojis_in_span(&mut span);
|
||||
}
|
||||
let sw = UnicodeWidthStr::width(span.content.as_ref());
|
||||
|
||||
if self.curr_width + sw > self.width {
|
||||
|
@ -200,10 +217,15 @@ impl<'a> TextPrinter<'a> {
|
|||
continue;
|
||||
}
|
||||
|
||||
let sw = UnicodeWidthStr::width(word);
|
||||
let cow = if self.emoji_shortcodes {
|
||||
Cow::Owned(replace_emojis_in_str(word))
|
||||
} else {
|
||||
Cow::Borrowed(word)
|
||||
};
|
||||
let sw = UnicodeWidthStr::width(cow.as_ref());
|
||||
|
||||
if sw > self.width {
|
||||
self.push_str_wrapped(word, style);
|
||||
self.push_str_wrapped(cow, style);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -211,13 +233,13 @@ impl<'a> TextPrinter<'a> {
|
|||
// Word doesn't fit on this line, so start a new one.
|
||||
self.commit();
|
||||
|
||||
if !self.literal && word.chars().all(char::is_whitespace) {
|
||||
if !self.literal && cow.chars().all(char::is_whitespace) {
|
||||
// Drop leading whitespace.
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
let span = Span::styled(word, style);
|
||||
let span = Span::styled(cow, style);
|
||||
self.curr_spans.push(span);
|
||||
self.curr_width += sw;
|
||||
}
|
||||
|
@ -229,14 +251,22 @@ impl<'a> TextPrinter<'a> {
|
|||
}
|
||||
|
||||
/// Push a [Line] into the printer.
|
||||
pub fn push_line(&mut self, line: Line<'a>) {
|
||||
pub fn push_line(&mut self, mut line: Line<'a>) {
|
||||
self.commit();
|
||||
if self.emoji_shortcodes {
|
||||
replace_emojis_in_line(&mut line);
|
||||
}
|
||||
self.text.lines.push(line);
|
||||
}
|
||||
|
||||
/// Push multiline [Text] into the printer.
|
||||
pub fn push_text(&mut self, text: Text<'a>) {
|
||||
pub fn push_text(&mut self, mut text: Text<'a>) {
|
||||
self.commit();
|
||||
if self.emoji_shortcodes {
|
||||
for line in &mut text.lines {
|
||||
replace_emojis_in_line(line);
|
||||
}
|
||||
}
|
||||
self.text.lines.extend(text.lines);
|
||||
}
|
||||
|
||||
|
|
|
@ -183,6 +183,7 @@ pub fn mock_tunables() -> TunableValues {
|
|||
TunableValues {
|
||||
default_room: None,
|
||||
log_level: Level::INFO,
|
||||
message_shortcode_display: false,
|
||||
reaction_display: true,
|
||||
reaction_shortcode_display: false,
|
||||
read_receipt_send: true,
|
||||
|
|
22
src/util.rs
22
src/util.rs
|
@ -147,6 +147,28 @@ pub fn join_cell_text<'a>(texts: Vec<(Text<'a>, usize)>, join: Span<'a>, style:
|
|||
text
|
||||
}
|
||||
|
||||
fn replace_emoji_in_grapheme(grapheme: &str) -> String {
|
||||
emojis::get(grapheme)
|
||||
.and_then(|emoji| emoji.shortcode())
|
||||
.map(|shortcode| format!(":{shortcode}:"))
|
||||
.unwrap_or_else(|| grapheme.to_owned())
|
||||
}
|
||||
|
||||
pub fn replace_emojis_in_str(s: &str) -> String {
|
||||
let graphemes = s.graphemes(true);
|
||||
graphemes.map(replace_emoji_in_grapheme).collect()
|
||||
}
|
||||
|
||||
pub fn replace_emojis_in_span(span: &mut Span) {
|
||||
span.content = Cow::Owned(replace_emojis_in_str(span.content.as_ref()))
|
||||
}
|
||||
|
||||
pub fn replace_emojis_in_line(line: &mut Line) {
|
||||
for span in &mut line.spans {
|
||||
replace_emojis_in_span(span);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod tests {
|
||||
use super::*;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue