Allow sending pre-blocks with language.

This commit is contained in:
John Preston 2023-10-04 20:26:22 +04:00
parent 02440524ea
commit 0d8717d48a
2 changed files with 61 additions and 20 deletions

View file

@ -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;
}
}

View file

@ -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,12 +3222,12 @@ 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))) {
for (const auto &tag : TextUtilities::SplitTags(currentTag)) {
if (tag == kTagCode || IsTagPre(tag)) {
return;
}
}
}
cursor.setPosition(from);
cursor.setPosition(till, QTextCursor::KeepAnchor);