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 { struct State {
QString link; QString link;
QString language;
uint32 mask = 0; uint32 mask = 0;
void set(EntityType type) { void set(EntityType type) {
@ -2118,28 +2119,34 @@ EntitiesInText ConvertTextTagsToEntities(const TextWithTags::Tags &tags) {
} }
for (const auto type : kInMaskTypes) { for (const auto type : kInMaskTypes) {
if (nextState.has(type) && !state.has(type)) { if (nextState.has(type) && !state.has(type)) {
openType(type); openType(type, nextState.language);
} }
} }
state = nextState; state = nextState;
}; };
const auto stateForTag = [&](const QString &tag) { const auto stateForTag = [&](const QString &tag) {
using Tags = Ui::InputField;
auto result = State(); auto result = State();
const auto list = SplitTags(tag); const auto list = SplitTags(tag);
const auto languageStart = Tags::kTagPre.size();
for (const auto &single : list) { for (const auto &single : list) {
if (single == Ui::InputField::kTagBold) { if (single == Tags::kTagBold) {
result.set(EntityType::Bold); result.set(EntityType::Bold);
} else if (single == Ui::InputField::kTagItalic) { } else if (single == Tags::kTagItalic) {
result.set(EntityType::Italic); result.set(EntityType::Italic);
} else if (single == Ui::InputField::kTagUnderline) { } else if (single == Tags::kTagUnderline) {
result.set(EntityType::Underline); result.set(EntityType::Underline);
} else if (single == Ui::InputField::kTagStrikeOut) { } else if (single == Tags::kTagStrikeOut) {
result.set(EntityType::StrikeOut); result.set(EntityType::StrikeOut);
} else if (single == Ui::InputField::kTagCode) { } else if (single == Tags::kTagCode) {
result.set(EntityType::Code); result.set(EntityType::Code);
} else if (single == Ui::InputField::kTagPre) { } else if (single == Tags::kTagPre) {
result.set(EntityType::Pre); 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); result.set(EntityType::Spoiler);
} else { } else {
result.link = single.toString(); result.link = single.toString();
@ -2235,8 +2242,17 @@ TextWithTags::Tags ConvertEntitiesToTextTags(
case EntityType::StrikeOut: case EntityType::StrikeOut:
push(Ui::InputField::kTagStrikeOut); push(Ui::InputField::kTagStrikeOut);
break; break;
case EntityType::Code: push(Ui::InputField::kTagCode); break; // #TODO entities case EntityType::Code: push(Ui::InputField::kTagCode); break;
case EntityType::Pre: push(Ui::InputField::kTagPre); 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; 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. // QTextCharFormat with the same properties, including kCustomEmojiText.
auto GlobalCustomEmojiCounter = 0; auto GlobalCustomEmojiCounter = 0;
[[nodiscard]] bool IsTagPre(QStringView tag) {
return tag.startsWith(kTagPre);
}
class InputDocument : public QTextDocument { class InputDocument : public QTextDocument {
public: public:
InputDocument(QObject *parent, const style::InputField &st); InputDocument(QObject *parent, const style::InputField &st);
@ -169,7 +173,7 @@ bool IsNewline(QChar ch) {
} }
auto found = false; auto found = false;
for (const auto &single : TextUtilities::SplitTags(existing.id)) { for (const auto &single : TextUtilities::SplitTags(existing.id)) {
const auto normalized = (single == QStringView(kTagPre)) const auto normalized = IsTagPre(single)
? QStringView(kTagCode) ? QStringView(kTagCode)
: single; : single;
if (checkingLink && IsValidMarkdownLink(single)) { if (checkingLink && IsValidMarkdownLink(single)) {
@ -207,6 +211,17 @@ bool IsNewline(QChar ch) {
return !CheckFullTextTag(textWithTags, tag).isEmpty(); 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 { class TagAccumulator {
public: public:
TagAccumulator(TextWithTags::Tags &tags) : _tags(tags) { TagAccumulator(TextWithTags::Tags &tags) : _tags(tags) {
@ -739,7 +754,7 @@ QTextCharFormat PrepareTagFormat(
font = font->underline(); font = font->underline();
} else if (tag == kTagStrikeOut) { } else if (tag == kTagStrikeOut) {
font = font->strikeout(); font = font->strikeout();
} else if (tag == kTagCode || tag == kTagPre) { } else if (tag == kTagCode || IsTagPre(tag)) {
color = st::defaultTextPalette.monoFg; color = st::defaultTextPalette.monoFg;
font = font->monospace(); font = font->monospace();
} else if (tag == kTagSpoiler) { } else if (tag == kTagSpoiler) {
@ -2668,11 +2683,21 @@ TextWithTags InputField::getTextWithAppliedMarkdown() const {
} }
addOriginalTextUpTill(tag.adjustedStart); addOriginalTextUpTill(tag.adjustedStart);
auto tagId = tag.tag;
auto entityStart = tag.adjustedStart + tagLength; auto entityStart = tag.adjustedStart + tagLength;
if (tag.tag == kTagPre) { if (tagId == kTagPre) {
// Remove redundant newlines for pre. // Remove redundant newlines for pre.
// If ``` is on a separate line add only one newline. // 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() && (result.text.isEmpty()
|| IsNewline(result.text[result.text.size() - 1]))) { || IsNewline(result.text[result.text.size() - 1]))) {
++entityStart; ++entityStart;
@ -2691,7 +2716,7 @@ TextWithTags InputField::getTextWithAppliedMarkdown() const {
result.tags.push_back(TextWithTags::Tag{ result.tags.push_back(TextWithTags::Tag{
int(result.text.size()), int(result.text.size()),
entityLength, entityLength,
tag.tag }); tagId });
result.text.append(base::StringViewMid( result.text.append(base::StringViewMid(
originalText, originalText,
entityStart, entityStart,
@ -3121,7 +3146,7 @@ void InputField::processInstantReplaces(const QString &appended) {
for (const auto &tag : _lastMarkdownTags) { for (const auto &tag : _lastMarkdownTags) {
if (tag.internalStart < position if (tag.internalStart < position
&& tag.internalStart + tag.internalLength >= position && tag.internalStart + tag.internalLength >= position
&& (tag.tag == kTagCode || tag.tag == kTagPre)) { && (tag.tag == kTagCode || IsTagPre(tag.tag))) {
return; return;
} }
} }
@ -3197,10 +3222,10 @@ void InputField::commitInstantReplacement(
const auto currentTag = cursor.charFormat().property( const auto currentTag = cursor.charFormat().property(
kTagProperty kTagProperty
).toString(); ).toString();
const auto currentTags = TextUtilities::SplitTags(currentTag); for (const auto &tag : TextUtilities::SplitTags(currentTag)) {
if (currentTags.contains(QStringView(kTagPre)) if (tag == kTagCode || IsTagPre(tag)) {
|| currentTags.contains(QStringView(kTagCode))) { return;
return; }
} }
} }
cursor.setPosition(from); cursor.setPosition(from);