Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Message parser #10

Merged
merged 15 commits into from
Nov 29, 2023
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ public DesignMessagesViewModel()
CurrentMessages.AddRange(new List<Message> {
message1,
CreateMessage("no", MessageType.DEFAULT, user2, now - new TimeSpan(0, 1, 0), message1),
CreateMessage("normal, ||spoiler|| *italic* ~~strike~~ **bold** **_mix_**\n```cs\ncodeblock```\n`inline`\nhttps://localhost.com\ne:weary: e<:salute:933778727547060234>\nu<@138397087229280257> c<#959372274299961344> r<@&985907053237268480>\n> quote1\n> quote2", MessageType.DEFAULT, user2, now - new TimeSpan(0, 2, 0), null)
});
}

Expand Down
56 changes: 53 additions & 3 deletions Turbulence.Desktop/Converters/MessageContentConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
using System.Globalization;
using Turbulence.Discord;
using Turbulence.Discord.Models.DiscordChannel;
using Turbulence.Discord.Utils;
using Turbulence.Discord.Utils.Parser;

namespace Turbulence.Desktop.Converters;

Expand All @@ -16,10 +18,58 @@ public class MessageContentConverter : IValueConverter
if (value is not Message message)
return null;

var res = new InlineCollection
var res = new InlineCollection();
var content = _client.GetMessageContent(message);
var nodes = MessageParser.Parse(content);

// Recursive function that turns nodes into avalonia inlines (run/span)
static Inline FromNode(Node node)
{
Inline ret;
switch (node.Type)
{
case NodeType.STRIKETHROUGH:
case NodeType.SPOILER:
case NodeType.BOLD:
case NodeType.UNDERLINE:
case NodeType.ITALIC:
case NodeType.CODE_INLINE:
case NodeType.CODE_BLOCK:
case NodeType.QUOTE_BLOCK:
ret = new Span();
ret.Classes.Add(node.Type.ToString());
if (node.Children != null)
foreach (var child in node.Children)
((Span)ret).Inlines.Add(FromNode(child));
break;
case NodeType.URL_WITH_PREVIEW:
case NodeType.URL_WITHOUT_PREVIEW:
ret = new Run(node.Url);
ret.Classes.Add("Url");
break;
case NodeType.USER:
case NodeType.CHANNEL:
case NodeType.ROLE:
//TODO: mentions
ret = new Run($"@{node.Id}");
break;
case NodeType.EMOJI_UNICODE_ENCODED:
case NodeType.EMOJI_CUSTOM:
//TODO: emojis
ret = new Run($":{node.Emoji}:");
break;
case NodeType.TEXT:
default:
ret = new Run(node.Text);
break;
}
return ret;
}
// Add nodes
foreach (var node in nodes)
{
_client.GetMessageContent(message),
};
res.Add(FromNode(node));
}
if (message.EditedTimestamp != null)
{
var editRun = new Run(" [Edited]");
Expand Down
34 changes: 34 additions & 0 deletions Turbulence.Desktop/Views/Main/MessageView.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,44 @@
<Setter Property="CornerRadius" Value="0" />
<Setter Property="Padding" Value="4" />
</Style>
<!-- Message Parsing -->
<Style Selector="Run.Edit">
<Setter Property="Foreground" Value="#949BA4" />
<Setter Property="FontSize" Value="10" />
</Style>
<Style Selector="Run.Url">
<Setter Property="TextDecorations" Value="Underline" />
<Setter Property="Foreground" Value="#00A8FC" />
</Style>
<Style Selector="Span.ITALIC">
<Setter Property="FontStyle" Value="Italic" />
</Style>
<Style Selector="Span.UNDERLINE">
<Setter Property="FontStyle" Value="Italic" />
</Style>
<Style Selector="Span.BOLD">
<Setter Property="FontWeight" Value="Bold" />
</Style>
<Style Selector="Span.UNDERLINE">
<Setter Property="FontStyle" Value="Italic" />
</Style>
<Style Selector="Span.STRIKETHROUGH">
<Setter Property="TextDecorations" Value="Strikethrough" />
</Style>
<Style Selector="Span.SPOILER">
<Setter Property="Background" Value="Gray" />
</Style>
<Style Selector="Span.QUOTE_BLOCK">
<!--TODO: make these indented-->
</Style>
<!--TODO: different fonts-->
<Style Selector="Span.CODE_BLOCK">
<!--TODO: make this one full size somehow-->
<Setter Property="Background" Value="Gray" />
</Style>
<Style Selector="Span.CODE_INLINE">
<Setter Property="Background" Value="Gray" />
</Style>
</UserControl.Styles>
<Grid ColumnDefinitions="Auto,*" Classes="Message">
<!-- Avatar -->
Expand Down
158 changes: 158 additions & 0 deletions Turbulence.Discord.Test/MessageParser/LexerTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
using Turbulence.Discord.Utils.Parser;

namespace Turbulence.Discord.Test.MessageParser;

public class LexerTests
{
[SetUp]
public void Setup()
{
}

private static void Test(string text, IEnumerable<Token> should)
{
var tokens = Lexer.Lex(text);
Assert.That(tokens, Is.Not.Null);
Assert.That(tokens, Is.Not.Empty);
foreach (var token in tokens)
{
Console.WriteLine(token);
}
Assert.That(tokens.Count(), Is.EqualTo(should.Count()));
for (var i = 0; i < tokens.Count(); i++)
{
var actual = tokens.ElementAt(i);
var expected = should.ElementAt(i);
Assert.Multiple(() =>
{
Assert.That(actual.Type, Is.EqualTo(expected.Type));
Assert.That(actual.Value, Is.EqualTo(expected.Value));
});
}
}

[Test]
public void Text()
{
var text = "text, **bold**, *italics*, _italics_, ~~strikethrough~~, ||spoiler||";
var tokens = new Token[]
{
new(TokenType.TEXT_INLINE, "text, "),
new(TokenType.STAR, "*"),
new(TokenType.STAR, "*"),
new(TokenType.TEXT_INLINE, "bold"),
new(TokenType.STAR, "*"),
new(TokenType.STAR, "*"),
new(TokenType.TEXT_INLINE, ", "),
new(TokenType.STAR, "*"),
new(TokenType.TEXT_INLINE, "italics"),
new(TokenType.STAR, "*"),
new(TokenType.TEXT_INLINE, ", "),
new(TokenType.UNDERSCORE, "_"),
new(TokenType.TEXT_INLINE, "italics"),
new(TokenType.UNDERSCORE, "_"),
new(TokenType.TEXT_INLINE, ", "),
new(TokenType.TILDE, "~"),
new(TokenType.TILDE, "~"),
new(TokenType.TEXT_INLINE, "strikethrough"),
new(TokenType.TILDE, "~"),
new(TokenType.TILDE, "~"),
new(TokenType.TEXT_INLINE, ", "),
new(TokenType.SPOILER_DELIMITER, "||"),
new(TokenType.TEXT_INLINE, "spoiler"),
new(TokenType.SPOILER_DELIMITER, "||"),
};
Test(text, tokens);
}

[Test]
public void Code()
{
var text = @"`code`
```cs
// *fake*
public void Real() {
Stuff();
}
```".Replace("\r\n", "\n");
var tokens = new Token[]
{
new(TokenType.CODE_INLINE_DELIMITER, "`"),
new(TokenType.TEXT_INLINE, "code"),
new(TokenType.CODE_INLINE_DELIMITER, "`"),
new(TokenType.NEWLINE, "\n"),
new(TokenType.CODE_BLOCK_DELIMITER, "```"),
new(TokenType.TEXT_INLINE, "cs"),
new(TokenType.NEWLINE, "\n"),
new(TokenType.TEXT_INLINE, "// "),
new(TokenType.STAR, "*"),
new(TokenType.TEXT_INLINE, "fake"),
new(TokenType.STAR, "*"),
new(TokenType.NEWLINE, "\n"),
new(TokenType.TEXT_INLINE, "public void Real() {"),
new(TokenType.NEWLINE, "\n"),
new(TokenType.TEXT_INLINE, " Stuff();"),
new(TokenType.NEWLINE, "\n"),
new(TokenType.TEXT_INLINE, "}"),
new(TokenType.NEWLINE, "\n"),
new(TokenType.CODE_BLOCK_DELIMITER, "```"),
};
Test(text, tokens);
}

[Test]
public void Mentions()
{
var text = "<@138397087229280257>\n<#959372274299961344>\n<@&985907053237268480>";
var tokens = new Token[]
{
new(TokenType.USER_MENTION, "<@138397087229280257>"),
new(TokenType.NEWLINE, "\n"),
new(TokenType.CHANNEL_MENTION, "<#959372274299961344>"),
new(TokenType.NEWLINE, "\n"),
new(TokenType.ROLE_MENTION, "<@&985907053237268480>"),
};
Test(text, tokens);
}

[Test]
public void Emojis()
{
var text = ":weary:\n<:salute:933778727547060234>";
var tokens = new Token[]
{
new(TokenType.EMOJI_UNICODE_ENCODED, ":weary:"),
new(TokenType.NEWLINE, "\n"),
new(TokenType.EMOJI_CUSTOM, "<:salute:933778727547060234>"),
};
Test(text, tokens);
}

[Test]
public void Links()
{
var text = "<https://google.com>\nhttps://google.com";
var tokens = new Token[]
{
new(TokenType.URL_WITHOUT_PREVIEW, "<https://google.com>"),
new(TokenType.NEWLINE, "\n"),
new(TokenType.URL_WITH_PREVIEW, "https://google.com"),
};
Test(text, tokens);
}

[Test]
public void Quote()
{
var text = "> line1\n> line2";
var tokens = new Token[]
{
new(TokenType.QUOTE_LINE_PREFIX, "> "),
new(TokenType.TEXT_INLINE, "line1"),
new(TokenType.NEWLINE, "\n"),
new(TokenType.QUOTE_LINE_PREFIX, "> "),
new(TokenType.TEXT_INLINE, "line2"),
};
Test(text, tokens);
}
}
Loading
Loading