From 0d8717d48ac3f2228399a1de78a28f0a7bbd0023 Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 4 Oct 2023 20:26:22 +0400 Subject: [PATCH] Allow sending pre-blocks with language. --- ui/text/text_entity.cpp | 36 ++++++++++++++++++------- ui/widgets/fields/input_field.cpp | 45 ++++++++++++++++++++++++------- 2 files changed, 61 insertions(+), 20 deletions(-) diff --git a/ui/text/text_entity.cpp b/ui/text/text_entity.cpp index 05633ec..4ca1999 100644 --- a/ui/text/text_entity.cpp +++ b/ui/text/text_entity.cpp @@ -2032,6 +2032,7 @@ EntitiesInText ConvertTextTagsToEntities(const TextWithTags::Tags &tags) { }; struct State { QString link; + QString language; uint32 mask = 0; void set(EntityType type) { @@ -2118,28 +2119,34 @@ EntitiesInText ConvertTextTagsToEntities(const TextWithTags::Tags &tags) { } for (const auto type : kInMaskTypes) { if (nextState.has(type) && !state.has(type)) { - openType(type); + openType(type, nextState.language); } } state = nextState; }; const auto stateForTag = [&](const QString &tag) { + using Tags = Ui::InputField; auto result = State(); const auto list = SplitTags(tag); + const auto languageStart = Tags::kTagPre.size(); for (const auto &single : list) { - if (single == Ui::InputField::kTagBold) { + if (single == Tags::kTagBold) { result.set(EntityType::Bold); - } else if (single == Ui::InputField::kTagItalic) { + } else if (single == Tags::kTagItalic) { result.set(EntityType::Italic); - } else if (single == Ui::InputField::kTagUnderline) { + } else if (single == Tags::kTagUnderline) { result.set(EntityType::Underline); - } else if (single == Ui::InputField::kTagStrikeOut) { + } else if (single == Tags::kTagStrikeOut) { result.set(EntityType::StrikeOut); - } else if (single == Ui::InputField::kTagCode) { + } else if (single == Tags::kTagCode) { result.set(EntityType::Code); - } else if (single == Ui::InputField::kTagPre) { + } else if (single == Tags::kTagPre) { result.set(EntityType::Pre); - } else if (single == Ui::InputField::kTagSpoiler) { + } else if (single.size() > languageStart + && single.startsWith(Tags::kTagPre)) { + result.set(EntityType::Pre); + result.language = single.mid(languageStart).toString(); + } else if (single == Tags::kTagSpoiler) { result.set(EntityType::Spoiler); } else { result.link = single.toString(); @@ -2235,8 +2242,17 @@ TextWithTags::Tags ConvertEntitiesToTextTags( case EntityType::StrikeOut: push(Ui::InputField::kTagStrikeOut); break; - case EntityType::Code: push(Ui::InputField::kTagCode); break; // #TODO entities - case EntityType::Pre: push(Ui::InputField::kTagPre); break; + case EntityType::Code: push(Ui::InputField::kTagCode); break; + case EntityType::Pre: { + if (!entity.data().isEmpty()) { + const auto language = QRegularExpression("^[a-z0-9\\-]+$"); + if (language.match(entity.data()).hasMatch()) { + push(Ui::InputField::kTagPre + entity.data()); + break; + } + } + push(Ui::InputField::kTagPre); + } break; case EntityType::Spoiler: push(Ui::InputField::kTagSpoiler); break; } } diff --git a/ui/widgets/fields/input_field.cpp b/ui/widgets/fields/input_field.cpp index 795228e..660ca56 100644 --- a/ui/widgets/fields/input_field.cpp +++ b/ui/widgets/fields/input_field.cpp @@ -65,6 +65,10 @@ const auto kNewlineChars = QString("\r\n") // QTextCharFormat with the same properties, including kCustomEmojiText. auto GlobalCustomEmojiCounter = 0; +[[nodiscard]] bool IsTagPre(QStringView tag) { + return tag.startsWith(kTagPre); +} + class InputDocument : public QTextDocument { public: InputDocument(QObject *parent, const style::InputField &st); @@ -169,7 +173,7 @@ bool IsNewline(QChar ch) { } auto found = false; for (const auto &single : TextUtilities::SplitTags(existing.id)) { - const auto normalized = (single == QStringView(kTagPre)) + const auto normalized = IsTagPre(single) ? QStringView(kTagCode) : single; if (checkingLink && IsValidMarkdownLink(single)) { @@ -207,6 +211,17 @@ bool IsNewline(QChar ch) { return !CheckFullTextTag(textWithTags, tag).isEmpty(); } +[[nodiscard]] QString ReadPreLanguageName( + const QString &text, + int preStart, + int preLength) { + auto view = QStringView(text).mid(preStart, preLength); + static const auto expression = QRegularExpression( + "^([a-zA-Z0-9\\-]+)[\\r\\n]"); + const auto m = expression.match(view); + return m.hasMatch() ? m.captured(1).toLower() : QString(); +} + class TagAccumulator { public: TagAccumulator(TextWithTags::Tags &tags) : _tags(tags) { @@ -739,7 +754,7 @@ QTextCharFormat PrepareTagFormat( font = font->underline(); } else if (tag == kTagStrikeOut) { font = font->strikeout(); - } else if (tag == kTagCode || tag == kTagPre) { + } else if (tag == kTagCode || IsTagPre(tag)) { color = st::defaultTextPalette.monoFg; font = font->monospace(); } else if (tag == kTagSpoiler) { @@ -2668,11 +2683,21 @@ TextWithTags InputField::getTextWithAppliedMarkdown() const { } addOriginalTextUpTill(tag.adjustedStart); + auto tagId = tag.tag; auto entityStart = tag.adjustedStart + tagLength; - if (tag.tag == kTagPre) { + if (tagId == kTagPre) { // Remove redundant newlines for pre. // If ``` is on a separate line add only one newline. - if (IsNewline(originalText[entityStart]) + const auto languageName = ReadPreLanguageName( + originalText, + entityStart, + entityLength); + if (!languageName.isEmpty()) { + // ```language-name{\n}code + entityStart += languageName.size() + 1; + entityLength -= languageName.size() + 1; + tagId += languageName; + } else if (IsNewline(originalText[entityStart]) && (result.text.isEmpty() || IsNewline(result.text[result.text.size() - 1]))) { ++entityStart; @@ -2691,7 +2716,7 @@ TextWithTags InputField::getTextWithAppliedMarkdown() const { result.tags.push_back(TextWithTags::Tag{ int(result.text.size()), entityLength, - tag.tag }); + tagId }); result.text.append(base::StringViewMid( originalText, entityStart, @@ -3121,7 +3146,7 @@ void InputField::processInstantReplaces(const QString &appended) { for (const auto &tag : _lastMarkdownTags) { if (tag.internalStart < position && tag.internalStart + tag.internalLength >= position - && (tag.tag == kTagCode || tag.tag == kTagPre)) { + && (tag.tag == kTagCode || IsTagPre(tag.tag))) { return; } } @@ -3197,10 +3222,10 @@ void InputField::commitInstantReplacement( const auto currentTag = cursor.charFormat().property( kTagProperty ).toString(); - const auto currentTags = TextUtilities::SplitTags(currentTag); - if (currentTags.contains(QStringView(kTagPre)) - || currentTags.contains(QStringView(kTagCode))) { - return; + for (const auto &tag : TextUtilities::SplitTags(currentTag)) { + if (tag == kTagCode || IsTagPre(tag)) { + return; + } } } cursor.setPosition(from);