diff --git a/src/message/compose.rs b/src/message/compose.rs index 2505853..8fd6ad0 100644 --- a/src/message/compose.rs +++ b/src/message/compose.rs @@ -53,8 +53,12 @@ impl SlashCommand { fn to_message(&self, input: &str) -> anyhow::Result { let msgtype = match self { SlashCommand::Emote => { - let html = text_to_html(input); - let msg = EmoteMessageEventContent::html(input, html); + let msg = if let Some(html) = text_to_html(input) { + EmoteMessageEventContent::html(input, html) + } else { + EmoteMessageEventContent::plain(input) + }; + MessageType::Emote(msg) }, SlashCommand::Html => { @@ -66,8 +70,7 @@ impl SlashCommand { MessageType::Text(msg) }, SlashCommand::Markdown => { - let html = text_to_html(input); - let msg = TextMessageEventContent::html(input, html); + let msg = text_to_message_content(input.to_string()); MessageType::Text(msg) }, SlashCommand::Confetti => { @@ -128,18 +131,43 @@ fn parse_slash_command(input: &str) -> anyhow::Result<(&str, SlashCommand)> { } } -fn text_to_html(input: &str) -> String { +/// Check whether this character is not used for markup in Markdown. +/// +/// Markdown uses just about every ASCII punctuation symbol in some way, especially +/// once autolinking is involved, so we really just check whether it's non-punctuation or +/// single/double quotations. +fn not_markdown_char(c: char) -> bool { + if !c.is_ascii_punctuation() { + return true; + } + + matches!(c, '"' | '\'') +} + +/// Check whether the input actually needs to be processed as Markdown. +fn not_markdown(input: &str) -> bool { + input.chars().all(not_markdown_char) +} + +fn text_to_html(input: &str) -> Option { + if not_markdown(input) { + return None; + } + let mut options = ComrakOptions::default(); options.extension.autolink = true; options.extension.shortcodes = true; options.extension.strikethrough = true; options.render.hardbreaks = true; - markdown_to_html(input, &options) + markdown_to_html(input, &options).into() } fn text_to_message_content(input: String) -> TextMessageEventContent { - let html = text_to_html(input.as_str()); - TextMessageEventContent::html(input, html) + if let Some(html) = text_to_html(input.as_str()) { + TextMessageEventContent::html(input, html) + } else { + TextMessageEventContent::plain(input) + } } pub fn text_to_message(input: String) -> RoomMessageEventContent { @@ -211,15 +239,18 @@ pub mod tests { assert_eq!(content.body, input); assert_eq!(content.formatted.unwrap().body, "

\u{2764}\u{FE0F}

\n"); - let input = "para 1\n\npara 2\n"; + let input = "para *1*\n\npara _2_\n"; let content = text_to_message_content(input.into()); assert_eq!(content.body, input); - assert_eq!(content.formatted.unwrap().body, "

para 1

\n

para 2

\n"); + assert_eq!( + content.formatted.unwrap().body, + "

para 1

\n

para 2

\n" + ); - let input = "line 1\nline 2\n"; + let input = "line 1\nline ~~2~~\n"; let content = text_to_message_content(input.into()); assert_eq!(content.body, input); - assert_eq!(content.formatted.unwrap().body, "

line 1
\nline 2

\n"); + assert_eq!(content.formatted.unwrap().body, "

line 1
\nline 2

\n"); let input = "# Heading\n## Subheading\n\ntext\n"; let content = text_to_message_content(input.into()); @@ -230,6 +261,61 @@ pub mod tests { ); } + #[test] + fn test_markdown_headers() { + let input = "hello\n=====\n"; + let content = text_to_message_content(input.into()); + assert_eq!(content.body, input); + assert_eq!(content.formatted.unwrap().body, "

hello

\n"); + + let input = "hello\n-----\n"; + let content = text_to_message_content(input.into()); + assert_eq!(content.body, input); + assert_eq!(content.formatted.unwrap().body, "

hello

\n"); + } + + #[test] + fn test_markdown_lists() { + let input = "- A\n- B\n- C\n"; + let content = text_to_message_content(input.into()); + assert_eq!(content.body, input); + assert_eq!( + content.formatted.unwrap().body, + "\n" + ); + + let input = "1) A\n2) B\n3) C\n"; + let content = text_to_message_content(input.into()); + assert_eq!(content.body, input); + assert_eq!( + content.formatted.unwrap().body, + "
    \n
  1. A
  2. \n
  3. B
  4. \n
  5. C
  6. \n
\n" + ); + } + + #[test] + fn test_no_markdown_conversion_on_simple_text() { + let input = "para 1\n\npara 2\n"; + let content = text_to_message_content(input.into()); + assert_eq!(content.body, input); + assert!(content.formatted.is_none()); + + let input = "line 1\nline 2\n"; + let content = text_to_message_content(input.into()); + assert_eq!(content.body, input); + assert!(content.formatted.is_none()); + + let input = "isn't markdown\n"; + let content = text_to_message_content(input.into()); + assert_eq!(content.body, input); + assert!(content.formatted.is_none()); + + let input = "\"scare quotes\"\n"; + let content = text_to_message_content(input.into()); + assert_eq!(content.body, input); + assert!(content.formatted.is_none()); + } + #[test] fn text_to_message_slash_commands() { let MessageType::Text(content) = text_to_message("/html bold".into()).msgtype else {