summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--src/Utils.cpp90
-rw-r--r--src/Utils.h2
-rw-r--r--src/timeline/InputBar.cpp9
-rw-r--r--src/timeline/InputBar.h4
4 files changed, 96 insertions, 9 deletions
diff --git a/src/Utils.cpp b/src/Utils.cpp
index 8a3b9e4c..32d435f0 100644
--- a/src/Utils.cpp
+++ b/src/Utils.cpp
@@ -17,6 +17,7 @@
 #include <QTextDocument>
 #include <QXmlStreamReader>
 
+#include <QTextBoundaryFinder>
 #include <cmath>
 #include <variant>
 
@@ -468,11 +469,92 @@ utils::escapeBlacklistedHtml(const QString &rawStr)
 }
 
 QString
-utils::markdownToHtml(const QString &text)
-{
-        const auto str      = text.toUtf8();
-        const char *tmp_buf = cmark_markdown_to_html(str.constData(), str.size(), CMARK_OPT_UNSAFE);
+utils::markdownToHtml(const QString &text, bool rainbowify)
+{
+        const auto str = text.toUtf8();
+        cmark_node *const node =
+          cmark_parse_document(str.constData(), str.size(), CMARK_OPT_UNSAFE);
+
+        if (rainbowify) {
+                // create iterator over node
+                cmark_iter *iter = cmark_iter_new(node);
+
+                cmark_event_type ev_type;
+
+                // First loop to get total text length
+                int textLen = 0;
+                while ((ev_type = cmark_iter_next(iter)) != CMARK_EVENT_DONE) {
+                        cmark_node *cur = cmark_iter_get_node(iter);
+                        // only text nodes (no code or semilar)
+                        if (cmark_node_get_type(cur) != CMARK_NODE_TEXT)
+                                continue;
+                        // count up by length of current node's text
+                        QTextBoundaryFinder tbf(QTextBoundaryFinder::BoundaryType::Grapheme,
+                                                QString(cmark_node_get_literal(cur)));
+                        while (tbf.toNextBoundary() != -1)
+                                textLen++;
+                }
+
+                // create new iter to start over
+                cmark_iter_free(iter);
+                iter = cmark_iter_new(node);
+
+                // Second loop to rainbowify
+                int charIdx = 0;
+                while ((ev_type = cmark_iter_next(iter)) != CMARK_EVENT_DONE) {
+                        cmark_node *cur = cmark_iter_get_node(iter);
+                        // only text nodes (no code or semilar)
+                        if (cmark_node_get_type(cur) != CMARK_NODE_TEXT)
+                                continue;
+
+                        // get text in current node
+                        QString nodeText(cmark_node_get_literal(cur));
+                        // create buffer to append rainbow text to
+                        QString buf;
+                        int boundaryStart = 0;
+                        int boundaryEnd   = 0;
+                        // use QTextBoundaryFinder to iterate ofer graphemes
+                        QTextBoundaryFinder tbf(QTextBoundaryFinder::BoundaryType::Grapheme,
+                                                nodeText);
+                        while ((boundaryEnd = tbf.toNextBoundary()) != -1) {
+                                // Split text to get current char
+                                auto curChar =
+                                  nodeText.midRef(boundaryStart, boundaryEnd - boundaryStart);
+                                boundaryStart = boundaryEnd;
+                                // Don't rainbowify whitespaces
+                                if (curChar.trimmed().isEmpty()) {
+                                        buf.append(curChar.toString());
+                                        continue;
+                                }
+
+                                // get correct color for char index
+                                auto color = QColor::fromHsvF(1.0 / textLen * charIdx, 1.0, 1.0);
+                                // format color for HTML
+                                auto colorString = color.name(QColor::NameFormat::HexRgb);
+                                // create HTML element for current char
+                                auto curCharColored = QString("<font color=\"%0\">%1</font>")
+                                                        .arg(colorString)
+                                                        .arg(curChar);
+                                // append colored HTML element to buffer
+                                buf.append(curCharColored);
+
+                                charIdx++;
+                        }
+
+                        // create HTML_INLINE node to prevent HTML from being escaped
+                        auto htmlNode = cmark_node_new(CMARK_NODE_HTML_INLINE);
+                        // set content of HTML node to buffer contents
+                        cmark_node_set_literal(htmlNode, buf.toUtf8().data());
+                        // replace current node with HTML node
+                        cmark_node_replace(cur, htmlNode);
+                        // free memory of old node
+                        cmark_node_free(cur);
+                }
+
+                cmark_iter_free(iter);
+        }
 
+        const char *tmp_buf = cmark_render_html(node, CMARK_OPT_UNSAFE);
         // Copy the null terminated output buffer.
         std::string html(tmp_buf);
 
diff --git a/src/Utils.h b/src/Utils.h
index f8ead68c..37c1baba 100644
--- a/src/Utils.h
+++ b/src/Utils.h
@@ -268,7 +268,7 @@ linkifyMessage(const QString &body);
 
 //! Convert the input markdown text to html.
 QString
-markdownToHtml(const QString &text);
+markdownToHtml(const QString &text, bool rainbowify = false);
 
 //! Escape every html tag, that was not whitelisted
 QString
diff --git a/src/timeline/InputBar.cpp b/src/timeline/InputBar.cpp
index b45827f6..53296efd 100644
--- a/src/timeline/InputBar.cpp
+++ b/src/timeline/InputBar.cpp
@@ -13,6 +13,7 @@
 #include <QStandardPaths>
 #include <QUrl>
 
+#include <QRegularExpression>
 #include <mtx/responses/common.hpp>
 #include <mtx/responses/media.hpp>
 
@@ -203,7 +204,7 @@ InputBar::send()
         auto wasEdit = !room->edit().isEmpty();
 
         if (text().startsWith('/')) {
-                int command_end = text().indexOf(' ');
+                int command_end = text().indexOf(QRegularExpression("\\s"));
                 if (command_end == -1)
                         command_end = text().size();
                 auto name = text().mid(1, command_end - 1);
@@ -255,7 +256,7 @@ InputBar::openFileSelection()
 }
 
 void
-InputBar::message(QString msg, MarkdownOverride useMarkdown)
+InputBar::message(QString msg, MarkdownOverride useMarkdown, bool rainbowify)
 {
         mtx::events::msg::Text text = {};
         text.body                   = msg.trimmed().toStdString();
@@ -263,7 +264,7 @@ InputBar::message(QString msg, MarkdownOverride useMarkdown)
         if ((ChatPage::instance()->userSettings()->markdown() &&
              useMarkdown == MarkdownOverride::NOT_SPECIFIED) ||
             useMarkdown == MarkdownOverride::ON) {
-                text.formatted_body = utils::markdownToHtml(msg).toStdString();
+                text.formatted_body = utils::markdownToHtml(msg, rainbowify).toStdString();
                 // Remove markdown links by completer
                 text.body =
                   msg.trimmed().replace(conf::strings::matrixToMarkdownLink, "\\1").toStdString();
@@ -526,6 +527,8 @@ InputBar::command(QString command, QString args)
                 message(args, MarkdownOverride::ON);
         } else if (command == "plain") {
                 message(args, MarkdownOverride::OFF);
+        } else if (command == "rainbow") {
+                message(args, MarkdownOverride::ON, true);
         }
 }
 
diff --git a/src/timeline/InputBar.h b/src/timeline/InputBar.h
index acd9e22c..f7a60488 100644
--- a/src/timeline/InputBar.h
+++ b/src/timeline/InputBar.h
@@ -53,7 +53,9 @@ public slots:
         void updateState(int selectionStart, int selectionEnd, int cursorPosition, QString text);
         void openFileSelection();
         bool uploading() const { return uploading_; }
-        void message(QString body, MarkdownOverride useMarkdown = MarkdownOverride::NOT_SPECIFIED);
+        void message(QString body,
+                     MarkdownOverride useMarkdown = MarkdownOverride::NOT_SPECIFIED,
+                     bool rainbowify              = false);
 
 private slots:
         void startTyping();