mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-07-27 16:03:03 +02:00
Parse bold / italic markdown entities.
This commit is contained in:
parent
da0d78135d
commit
eaf91bba58
8 changed files with 272 additions and 184 deletions
|
@ -386,7 +386,7 @@ void InnerWidget::clearAndRequestLog() {
|
|||
|
||||
void InnerWidget::updateEmptyText() {
|
||||
auto options = _defaultOptions;
|
||||
options.flags |= TextParseMono; // For bold :/
|
||||
options.flags |= TextParseMarkdown;
|
||||
auto hasSearch = !_searchQuery.isEmpty();
|
||||
auto hasFilter = (_filter.flags != 0) || !_filter.allUsers;
|
||||
auto text = TextWithEntities { lang((hasSearch || hasFilter) ? lng_admin_log_no_results_title : lng_admin_log_no_events_title) };
|
||||
|
|
|
@ -49,7 +49,7 @@ TextParseOptions _webpageTitleOptions = {
|
|||
Qt::LayoutDirectionAuto, // dir
|
||||
};
|
||||
TextParseOptions _webpageDescriptionOptions = {
|
||||
TextParseLinks | TextParseMentions | TextParseHashtags | TextParseMultiline | TextParseRichText | TextParseMono, // flags
|
||||
TextParseLinks | TextParseMentions | TextParseHashtags | TextParseMultiline | TextParseRichText | TextParseMarkdown, // flags
|
||||
0, // maxw
|
||||
0, // maxh
|
||||
Qt::LayoutDirectionAuto, // dir
|
||||
|
|
|
@ -44,13 +44,13 @@ TextParseOptions _textDlgOptions = {
|
|||
Qt::LayoutDirectionAuto, // lang-dependent
|
||||
};
|
||||
TextParseOptions _historyTextOptions = {
|
||||
TextParseLinks | TextParseMentions | TextParseHashtags | TextParseMultiline | TextParseRichText | TextParseMono, // flags
|
||||
TextParseLinks | TextParseMentions | TextParseHashtags | TextParseMultiline | TextParseRichText | TextParseMarkdown, // flags
|
||||
0, // maxw
|
||||
0, // maxh
|
||||
Qt::LayoutDirectionAuto, // dir
|
||||
};
|
||||
TextParseOptions _historyBotOptions = {
|
||||
TextParseLinks | TextParseMentions | TextParseHashtags | TextParseBotCommands | TextParseMultiline | TextParseRichText | TextParseMono, // flags
|
||||
TextParseLinks | TextParseMentions | TextParseHashtags | TextParseBotCommands | TextParseMultiline | TextParseRichText | TextParseMarkdown, // flags
|
||||
0, // maxw
|
||||
0, // maxh
|
||||
Qt::LayoutDirectionAuto, // dir
|
||||
|
|
|
@ -41,7 +41,7 @@ namespace Layout {
|
|||
namespace {
|
||||
|
||||
TextParseOptions _documentNameOptions = {
|
||||
TextParseMultiline | TextParseRichText | TextParseLinks | TextParseHashtags | TextParseMentions | TextParseBotCommands | TextParseMono, // flags
|
||||
TextParseMultiline | TextParseRichText | TextParseLinks | TextParseHashtags | TextParseMentions | TextParseBotCommands | TextParseMarkdown, // flags
|
||||
0, // maxw
|
||||
0, // maxh
|
||||
Qt::LayoutDirectionAuto, // dir
|
||||
|
|
|
@ -513,8 +513,8 @@ public:
|
|||
bool parseMentions = (options.flags & TextParseMentions);
|
||||
bool parseHashtags = (options.flags & TextParseHashtags);
|
||||
bool parseBotCommands = (options.flags & TextParseBotCommands);
|
||||
bool parseMono = (options.flags & TextParseMono);
|
||||
if (!parseMentions || !parseHashtags || !parseBotCommands || !parseMono) {
|
||||
bool parseMarkdown = (options.flags & TextParseMarkdown);
|
||||
if (!parseMentions || !parseHashtags || !parseBotCommands || !parseMarkdown) {
|
||||
int32 i = 0, l = preparsed.size();
|
||||
source.entities.clear();
|
||||
source.entities.reserve(l);
|
||||
|
@ -524,7 +524,7 @@ public:
|
|||
if (((type == EntityInTextMention || type == EntityInTextMentionName) && !parseMentions) ||
|
||||
(type == EntityInTextHashtag && !parseHashtags) ||
|
||||
(type == EntityInTextBotCommand && !parseBotCommands) ||
|
||||
((type == EntityInTextBold || type == EntityInTextItalic || type == EntityInTextCode || type == EntityInTextPre) && !parseMono)) {
|
||||
((type == EntityInTextBold || type == EntityInTextItalic || type == EntityInTextCode || type == EntityInTextPre) && !parseMarkdown)) {
|
||||
continue;
|
||||
}
|
||||
source.entities.push_back(preparsed.at(i));
|
||||
|
|
|
@ -43,24 +43,40 @@ QString ExpressionMailNameAtEnd() {
|
|||
return qsl("[a-zA-Z\\-_\\.0-9]{1,256}$");
|
||||
}
|
||||
|
||||
QString ExpressionSeparators(const QString &additional) {
|
||||
return qsl("\\s\\.,:;<>|'\"\\[\\]\\{\\}\\~\\!\\%\\^\\(\\)\\-\\+=\\x10") + additional;
|
||||
}
|
||||
|
||||
QString ExpressionHashtag() {
|
||||
return qsl("(^|[\\s\\.,:;<>|'\"\\[\\]\\{\\}`\\~\\!\\%\\^\\*\\(\\)\\-\\+=\\x10])#[\\w]{2,64}([\\W]|$)");
|
||||
return qsl("(^|[") + ExpressionSeparators(qsl("`\\*/")) + qsl("])#[\\w]{2,64}([\\W]|$)");
|
||||
}
|
||||
|
||||
QString ExpressionMention() {
|
||||
return qsl("(^|[\\s\\.,:;<>|'\"\\[\\]\\{\\}`\\~\\!\\%\\^\\*\\(\\)\\-\\+=\\x10])@[A-Za-z_0-9]{1,32}([\\W]|$)");
|
||||
return qsl("(^|[") + ExpressionSeparators(qsl("`\\*/")) + qsl("])@[A-Za-z_0-9]{1,32}([\\W]|$)");
|
||||
}
|
||||
|
||||
QString ExpressionBotCommand() {
|
||||
return qsl("(^|[\\s\\.,:;<>|'\"\\[\\]\\{\\}`\\~\\!\\%\\^\\*\\(\\)\\-\\+=\\x10])/[A-Za-z_0-9]{1,64}(@[A-Za-z_0-9]{5,32})?([\\W]|$)");
|
||||
return qsl("(^|[") + ExpressionSeparators(qsl("`\\*")) + qsl("])/[A-Za-z_0-9]{1,64}(@[A-Za-z_0-9]{5,32})?([\\W]|$)");
|
||||
}
|
||||
|
||||
QString ExpressionMonoInline() { // pre
|
||||
return qsl("(^|[\\s\\.,:;<>|'\"\\[\\]\\{\\}`\\~\\!\\?\\%\\^\\*\\(\\)\\-\\+=\\x10])(````?)[\\s\\S]+?(````?)([\\s\\.,:;<>|'\"\\[\\]\\{\\}`\\~\\!\\?\\%\\^\\*\\(\\)\\-\\+=\\x10]|$)");
|
||||
QString ExpressionMarkdownBold() {
|
||||
auto separators = ExpressionSeparators(qsl("`/"));
|
||||
return qsl("(^|[") + separators + qsl("])(\\*\\*)[\\s\\S]+?(\\*\\*)([") + separators + qsl("]|$)");
|
||||
}
|
||||
|
||||
QString ExpressionMonoBlock() { // code
|
||||
return qsl("(^|[\\s\\.,:;<>|'\"\\[\\]\\{\\}`\\~\\!\\?\\%\\^\\*\\(\\)\\-\\+=\\x10])(`)[^\\n]+?(`)([\\s\\.,:;<>|'\"\\[\\]\\{\\}`\\~\\!\\?\\%\\^\\*\\(\\)\\-\\+=\\x10]|$)");
|
||||
QString ExpressionMarkdownItalic() {
|
||||
auto separators = ExpressionSeparators(qsl("`\\*/"));
|
||||
return qsl("(^|[") + separators + qsl("])(__)[\\s\\S]+?(__)([") + separators + qsl("]|$)");
|
||||
}
|
||||
|
||||
QString ExpressionMarkdownMonoInline() { // code
|
||||
auto separators = ExpressionSeparators(qsl("\\*/"));
|
||||
return qsl("(^|[") + separators + qsl("])(`)[^\\n]+?(`)([") + separators + qsl("]|$)");
|
||||
}
|
||||
|
||||
QString ExpressionMarkdownMonoBlock() { // pre
|
||||
auto separators = ExpressionSeparators(qsl("\\*/"));
|
||||
return qsl("(^|[") + separators + qsl("])(````?)[\\s\\S]+?(````?)([") + separators + qsl("]|$)");
|
||||
}
|
||||
|
||||
QRegularExpression CreateRegExp(const QString &expression) {
|
||||
|
@ -1146,13 +1162,23 @@ const QRegularExpression &RegExpBotCommand() {
|
|||
return result;
|
||||
}
|
||||
|
||||
const QRegularExpression &RegExpMonoInline() {
|
||||
static const auto result = CreateRegExp(ExpressionMonoInline());
|
||||
const QRegularExpression &RegExpMarkdownBold() {
|
||||
static const auto result = CreateRegExp(ExpressionMarkdownBold());
|
||||
return result;
|
||||
}
|
||||
|
||||
const QRegularExpression &RegExpMonoBlock() {
|
||||
static const auto result = CreateRegExp(ExpressionMonoBlock());
|
||||
const QRegularExpression &RegExpMarkdownItalic() {
|
||||
static const auto result = CreateRegExp(ExpressionMarkdownItalic());
|
||||
return result;
|
||||
}
|
||||
|
||||
const QRegularExpression &RegExpMarkdownMonoInline() {
|
||||
static const auto result = CreateRegExp(ExpressionMarkdownMonoInline());
|
||||
return result;
|
||||
}
|
||||
|
||||
const QRegularExpression &RegExpMarkdownMonoBlock() {
|
||||
static const auto result = CreateRegExp(ExpressionMarkdownMonoBlock());
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -1471,22 +1497,25 @@ EntitiesInText EntitiesFromMTP(const QVector<MTPMessageEntity> &entities) {
|
|||
return result;
|
||||
}
|
||||
|
||||
MTPVector<MTPMessageEntity> EntitiesToMTP(const EntitiesInText &links, ConvertOption option) {
|
||||
MTPVector<MTPMessageEntity> EntitiesToMTP(const EntitiesInText &entities, ConvertOption option) {
|
||||
auto v = QVector<MTPMessageEntity>();
|
||||
v.reserve(links.size());
|
||||
for_const (auto &link, links) {
|
||||
if (link.length() <= 0) continue;
|
||||
v.reserve(entities.size());
|
||||
for_const (auto &entity, entities) {
|
||||
if (entity.length() <= 0) continue;
|
||||
if (option == ConvertOption::SkipLocal
|
||||
&& link.type() != EntityInTextCode
|
||||
&& link.type() != EntityInTextPre
|
||||
&& link.type() != EntityInTextMentionName) {
|
||||
&& entity.type() != EntityInTextBold
|
||||
&& entity.type() != EntityInTextItalic
|
||||
&& entity.type() != EntityInTextCode
|
||||
&& entity.type() != EntityInTextPre
|
||||
&& entity.type() != EntityInTextMentionName) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto offset = MTP_int(link.offset()), length = MTP_int(link.length());
|
||||
switch (link.type()) {
|
||||
auto offset = MTP_int(entity.offset());
|
||||
auto length = MTP_int(entity.length());
|
||||
switch (entity.type()) {
|
||||
case EntityInTextUrl: v.push_back(MTP_messageEntityUrl(offset, length)); break;
|
||||
case EntityInTextCustomUrl: v.push_back(MTP_messageEntityTextUrl(offset, length, MTP_string(link.data()))); break;
|
||||
case EntityInTextCustomUrl: v.push_back(MTP_messageEntityTextUrl(offset, length, MTP_string(entity.data()))); break;
|
||||
case EntityInTextEmail: v.push_back(MTP_messageEntityEmail(offset, length)); break;
|
||||
case EntityInTextHashtag: v.push_back(MTP_messageEntityHashtag(offset, length)); break;
|
||||
case EntityInTextMention: v.push_back(MTP_messageEntityMention(offset, length)); break;
|
||||
|
@ -1499,7 +1528,7 @@ MTPVector<MTPMessageEntity> EntitiesToMTP(const EntitiesInText &links, ConvertOp
|
|||
return MTP_inputUser(MTP_int(fields.userId), MTP_long(fields.accessHash));
|
||||
}
|
||||
return MTP_inputUserEmpty();
|
||||
})(link.data());
|
||||
})(entity.data());
|
||||
if (inputUser.type() != mtpc_inputUserEmpty) {
|
||||
v.push_back(MTP_inputMessageEntityMentionName(offset, length, inputUser));
|
||||
}
|
||||
|
@ -1508,173 +1537,226 @@ MTPVector<MTPMessageEntity> EntitiesToMTP(const EntitiesInText &links, ConvertOp
|
|||
case EntityInTextBold: v.push_back(MTP_messageEntityBold(offset, length)); break;
|
||||
case EntityInTextItalic: v.push_back(MTP_messageEntityItalic(offset, length)); break;
|
||||
case EntityInTextCode: v.push_back(MTP_messageEntityCode(offset, length)); break;
|
||||
case EntityInTextPre: v.push_back(MTP_messageEntityPre(offset, length, MTP_string(link.data()))); break;
|
||||
case EntityInTextPre: v.push_back(MTP_messageEntityPre(offset, length, MTP_string(entity.data()))); break;
|
||||
}
|
||||
}
|
||||
return MTP_vector<MTPMessageEntity>(std::move(v));
|
||||
}
|
||||
|
||||
// Some code is duplicated in flattextarea.cpp!
|
||||
void ParseEntities(TextWithEntities &result, int32 flags, bool rich) {
|
||||
auto newEntities = EntitiesInText();
|
||||
struct MarkdownPart {
|
||||
EntityInTextType type = EntityInTextInvalid;
|
||||
int outerStart = 0;
|
||||
int innerStart = 0;
|
||||
int innerEnd = 0;
|
||||
int outerEnd = 0;
|
||||
bool addNewlineBefore = false;
|
||||
bool addNewlineAfter = false;
|
||||
};
|
||||
|
||||
bool withHashtags = (flags & TextParseHashtags);
|
||||
bool withMentions = (flags & TextParseMentions);
|
||||
bool withBotCommands = (flags & TextParseBotCommands);
|
||||
bool withMono = (flags & TextParseMono);
|
||||
MarkdownPart GetMarkdownPart(EntityInTextType type, const QString &text, int matchFromOffset, bool rich) {
|
||||
auto result = MarkdownPart();
|
||||
auto regexp = [type] {
|
||||
switch (type) {
|
||||
case EntityInTextBold: return RegExpMarkdownBold();
|
||||
case EntityInTextItalic: return RegExpMarkdownItalic();
|
||||
case EntityInTextCode: return RegExpMarkdownMonoInline();
|
||||
case EntityInTextPre: return RegExpMarkdownMonoBlock();
|
||||
}
|
||||
Unexpected("Type in GetMardownPart()");
|
||||
};
|
||||
|
||||
if (withMono) { // parse mono entities (code and pre)
|
||||
int existingEntityIndex = 0, existingEntitiesCount = result.entities.size();
|
||||
int existingEntityShiftLeft = 0;
|
||||
auto match = regexp().match(text, matchFromOffset);
|
||||
if (!match.hasMatch()) {
|
||||
return result;
|
||||
}
|
||||
|
||||
QString newText;
|
||||
result.outerStart = match.capturedStart();
|
||||
result.outerEnd = match.capturedEnd();
|
||||
if (!match.capturedRef(1).isEmpty()) {
|
||||
++result.outerStart;
|
||||
}
|
||||
if (!match.capturedRef(4).isEmpty()) {
|
||||
--result.outerEnd;
|
||||
}
|
||||
result.innerStart = result.outerStart + match.capturedLength(2);
|
||||
result.innerEnd = result.outerEnd - match.capturedLength(3);
|
||||
result.type = type;
|
||||
return result;
|
||||
}
|
||||
|
||||
int32 offset = 0, matchOffset = offset, len = result.text.size(), commandOffset = rich ? 0 : len;
|
||||
bool inLink = false, commandIsLink = false;
|
||||
const QChar *start = result.text.constData();
|
||||
for (; matchOffset < len;) {
|
||||
if (commandOffset <= matchOffset) {
|
||||
for (commandOffset = matchOffset; commandOffset < len; ++commandOffset) {
|
||||
if (*(start + commandOffset) == TextCommand) {
|
||||
inLink = commandIsLink;
|
||||
commandIsLink = textcmdStartsLink(start, len, commandOffset);
|
||||
void AdjustMarkdownPrePart(MarkdownPart &result, const TextWithEntities &text, bool rich) {
|
||||
auto start = text.text.constData();
|
||||
auto length = text.text.size();
|
||||
auto lastEntityBeforeEnd = 0;
|
||||
auto firstEntityInsideStart = result.innerEnd;
|
||||
auto lastEntityInsideEnd = result.innerStart;
|
||||
auto firstEntityAfterStart = length;
|
||||
for_const (auto &entity, text.entities) {
|
||||
if (entity.offset() < result.outerStart) {
|
||||
lastEntityBeforeEnd = entity.offset() + entity.length();
|
||||
} else if (entity.offset() >= result.outerEnd) {
|
||||
firstEntityAfterStart = entity.offset();
|
||||
break;
|
||||
} else if (entity.offset() >= result.innerStart) {
|
||||
accumulate_min(firstEntityInsideStart, entity.offset());
|
||||
lastEntityInsideEnd = entity.offset() + entity.length();
|
||||
}
|
||||
}
|
||||
if (commandOffset >= len) {
|
||||
inLink = commandIsLink;
|
||||
commandIsLink = false;
|
||||
while (result.outerStart > lastEntityBeforeEnd
|
||||
&& chIsSpace(*(start + result.outerStart - 1), rich)
|
||||
&& !chIsNewline(*(start + result.outerStart - 1))) {
|
||||
--result.outerStart;
|
||||
}
|
||||
}
|
||||
auto mPre = RegExpMonoInline().match(result.text, matchOffset);
|
||||
auto mCode = RegExpMonoBlock().match(result.text, matchOffset);
|
||||
if (!mPre.hasMatch() && !mCode.hasMatch()) break;
|
||||
result.addNewlineBefore = (result.outerStart > 0 && !chIsNewline(*(start + result.outerStart - 1)));
|
||||
|
||||
int preStart = mPre.hasMatch() ? mPre.capturedStart() : INT_MAX,
|
||||
preEnd = mPre.hasMatch() ? mPre.capturedEnd() : INT_MAX,
|
||||
codeStart = mCode.hasMatch() ? mCode.capturedStart() : INT_MAX,
|
||||
codeEnd = mCode.hasMatch() ? mCode.capturedEnd() : INT_MAX,
|
||||
tagStart, tagEnd;
|
||||
if (mPre.hasMatch()) {
|
||||
if (!mPre.capturedRef(1).isEmpty()) {
|
||||
++preStart;
|
||||
}
|
||||
if (!mPre.capturedRef(4).isEmpty()) {
|
||||
--preEnd;
|
||||
}
|
||||
}
|
||||
if (mCode.hasMatch()) {
|
||||
if (!mCode.capturedRef(1).isEmpty()) {
|
||||
++codeStart;
|
||||
}
|
||||
if (!mCode.capturedRef(4).isEmpty()) {
|
||||
--codeEnd;
|
||||
}
|
||||
}
|
||||
|
||||
bool pre = (preStart <= codeStart);
|
||||
auto mTag = pre ? mPre : mCode;
|
||||
if (pre) {
|
||||
tagStart = preStart;
|
||||
tagEnd = preEnd;
|
||||
} else {
|
||||
tagStart = codeStart;
|
||||
tagEnd = codeEnd;
|
||||
}
|
||||
|
||||
bool inCommand = checkTagStartInCommand(start, len, tagStart, commandOffset, commandIsLink, inLink);
|
||||
if (inCommand || inLink) {
|
||||
matchOffset = commandOffset;
|
||||
continue;
|
||||
}
|
||||
|
||||
bool addNewlineBefore = false, addNewlineAfter = false;
|
||||
int32 outerStart = tagStart, outerEnd = tagEnd;
|
||||
int32 innerStart = tagStart + mTag.capturedLength(2), innerEnd = tagEnd - mTag.capturedLength(3);
|
||||
|
||||
// Check if start or end sequences intersect any existing entity.
|
||||
int intersectedEntityEnd = 0;
|
||||
for_const (auto &entity, result.entities) {
|
||||
if (qMin(innerStart, entity.offset() + entity.length()) > qMax(outerStart, entity.offset()) ||
|
||||
qMin(outerEnd, entity.offset() + entity.length()) > qMax(innerEnd, entity.offset())) {
|
||||
intersectedEntityEnd = entity.offset() + entity.length();
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (intersectedEntityEnd > 0) {
|
||||
matchOffset = qMax(innerStart, intersectedEntityEnd);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (newText.isEmpty()) newText.reserve(result.text.size());
|
||||
if (pre) {
|
||||
while (outerStart > 0 && chIsSpace(*(start + outerStart - 1), rich) && !chIsNewline(*(start + outerStart - 1))) {
|
||||
--outerStart;
|
||||
}
|
||||
addNewlineBefore = (outerStart > 0 && !chIsNewline(*(start + outerStart - 1)));
|
||||
|
||||
for (int32 testInnerStart = innerStart; testInnerStart < innerEnd; ++testInnerStart) {
|
||||
for (auto testInnerStart = result.innerStart; testInnerStart < firstEntityInsideStart; ++testInnerStart) {
|
||||
if (chIsNewline(*(start + testInnerStart))) {
|
||||
innerStart = testInnerStart + 1;
|
||||
result.innerStart = testInnerStart + 1;
|
||||
break;
|
||||
} else if (!chIsSpace(*(start + testInnerStart))) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (int32 testInnerEnd = innerEnd; innerStart < testInnerEnd;) {
|
||||
for (auto testInnerEnd = result.innerEnd; lastEntityInsideEnd < testInnerEnd;) {
|
||||
--testInnerEnd;
|
||||
if (chIsNewline(*(start + testInnerEnd))) {
|
||||
innerEnd = testInnerEnd;
|
||||
result.innerEnd = testInnerEnd;
|
||||
break;
|
||||
} else if (!chIsSpace(*(start + testInnerEnd))) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
while (outerEnd < len && chIsSpace(*(start + outerEnd)) && !chIsNewline(*(start + outerEnd))) {
|
||||
++outerEnd;
|
||||
while (result.outerEnd < firstEntityAfterStart
|
||||
&& chIsSpace(*(start + result.outerEnd))
|
||||
&& !chIsNewline(*(start + result.outerEnd))) {
|
||||
++result.outerEnd;
|
||||
}
|
||||
addNewlineAfter = (outerEnd < len && !chIsNewline(*(start + outerEnd)));
|
||||
result.addNewlineAfter = (result.outerEnd < length && !chIsNewline(*(start + result.outerEnd)));
|
||||
}
|
||||
|
||||
void ParseMarkdown(TextWithEntities &result, bool rich) {
|
||||
if (result.empty()) {
|
||||
return;
|
||||
}
|
||||
auto newResult = TextWithEntities();
|
||||
|
||||
auto existingEntityIndex = 0;
|
||||
auto existingEntitiesCount = result.entities.size();
|
||||
auto existingEntityShiftLeft = 0;
|
||||
|
||||
auto copyFromOffset = 0;
|
||||
auto matchFromOffset = 0;
|
||||
auto length = result.text.size();
|
||||
auto nextCommandOffset = rich ? 0 : length;
|
||||
auto inLink = false;
|
||||
auto commandIsLink = false;
|
||||
const auto start = result.text.constData();
|
||||
for (; matchFromOffset < length;) {
|
||||
if (nextCommandOffset <= matchFromOffset) {
|
||||
for (nextCommandOffset = matchFromOffset; nextCommandOffset != length; ++nextCommandOffset) {
|
||||
if (*(start + nextCommandOffset) == TextCommand) {
|
||||
inLink = commandIsLink;
|
||||
commandIsLink = textcmdStartsLink(start, length, nextCommandOffset);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (nextCommandOffset >= length) {
|
||||
inLink = commandIsLink;
|
||||
commandIsLink = false;
|
||||
}
|
||||
}
|
||||
auto part = MarkdownPart();
|
||||
auto testPart = [&part, &result, matchFromOffset, rich](EntityInTextType type) {
|
||||
auto test = GetMarkdownPart(type, result.text, matchFromOffset, rich);
|
||||
if (test.type != EntityInTextInvalid) {
|
||||
if (part.type == EntityInTextInvalid || part.outerStart > test.outerStart) {
|
||||
part = test;
|
||||
}
|
||||
}
|
||||
};
|
||||
testPart(EntityInTextBold);
|
||||
testPart(EntityInTextItalic);
|
||||
testPart(EntityInTextPre);
|
||||
testPart(EntityInTextCode);
|
||||
if (part.type == EntityInTextInvalid) {
|
||||
break;
|
||||
}
|
||||
|
||||
for (; existingEntityIndex < existingEntitiesCount && result.entities[existingEntityIndex].offset() < innerStart; ++existingEntityIndex) {
|
||||
// Check if start sequence intersects a command.
|
||||
auto inCommand = checkTagStartInCommand(start, length, part.outerStart, nextCommandOffset, commandIsLink, inLink);
|
||||
if (inCommand || inLink) {
|
||||
matchFromOffset = nextCommandOffset;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if start or end sequences intersect any existing entity.
|
||||
auto intersectedEntityEnd = 0;
|
||||
for_const (auto &entity, result.entities) {
|
||||
if (qMin(part.innerStart, entity.offset() + entity.length()) > qMax(part.outerStart, entity.offset()) ||
|
||||
qMin(part.outerEnd, entity.offset() + entity.length()) > qMax(part.innerEnd, entity.offset())) {
|
||||
intersectedEntityEnd = entity.offset() + entity.length();
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (intersectedEntityEnd > 0) {
|
||||
matchFromOffset = qMax(part.innerStart, intersectedEntityEnd);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (part.type == EntityInTextPre) {
|
||||
AdjustMarkdownPrePart(part, result, rich);
|
||||
}
|
||||
|
||||
if (newResult.text.isEmpty()) newResult.text.reserve(result.text.size());
|
||||
for (; existingEntityIndex < existingEntitiesCount && result.entities[existingEntityIndex].offset() < part.innerStart; ++existingEntityIndex) {
|
||||
auto &entity = result.entities[existingEntityIndex];
|
||||
newEntities.push_back(entity);
|
||||
newEntities.back().shiftLeft(existingEntityShiftLeft);
|
||||
newResult.entities.push_back(entity);
|
||||
newResult.entities.back().shiftLeft(existingEntityShiftLeft);
|
||||
}
|
||||
if (outerStart > offset) newText.append(start + offset, outerStart - offset);
|
||||
if (addNewlineBefore) newText.append('\n');
|
||||
existingEntityShiftLeft += (innerStart - outerStart) - (addNewlineBefore ? 1 : 0);
|
||||
if (part.outerStart > copyFromOffset) {
|
||||
newResult.text.append(start + copyFromOffset, part.outerStart - copyFromOffset);
|
||||
}
|
||||
if (part.addNewlineBefore) newResult.text.append('\n');
|
||||
existingEntityShiftLeft += (part.innerStart - part.outerStart) - (part.addNewlineBefore ? 1 : 0);
|
||||
|
||||
int entityStart = newText.size(), entityLength = innerEnd - innerStart;
|
||||
newEntities.push_back(EntityInText(pre ? EntityInTextPre : EntityInTextCode, entityStart, entityLength));
|
||||
auto entityStart = newResult.text.size();
|
||||
auto entityLength = part.innerEnd - part.innerStart;
|
||||
newResult.entities.push_back(EntityInText(part.type, entityStart, entityLength));
|
||||
|
||||
for (; existingEntityIndex < existingEntitiesCount && result.entities[existingEntityIndex].offset() <= innerEnd; ++existingEntityIndex) {
|
||||
for (; existingEntityIndex < existingEntitiesCount && result.entities[existingEntityIndex].offset() <= part.innerEnd; ++existingEntityIndex) {
|
||||
auto &entity = result.entities[existingEntityIndex];
|
||||
newEntities.push_back(entity);
|
||||
newEntities.back().shiftLeft(existingEntityShiftLeft);
|
||||
newResult.entities.push_back(entity);
|
||||
newResult.entities.back().shiftLeft(existingEntityShiftLeft);
|
||||
}
|
||||
newText.append(start + innerStart, entityLength);
|
||||
if (addNewlineAfter) newText.append('\n');
|
||||
existingEntityShiftLeft += (outerEnd - innerEnd) - (addNewlineAfter ? 1 : 0);
|
||||
newResult.text.append(start + part.innerStart, entityLength);
|
||||
if (part.addNewlineAfter) newResult.text.append('\n');
|
||||
existingEntityShiftLeft += (part.outerEnd - part.innerEnd) - (part.addNewlineAfter ? 1 : 0);
|
||||
|
||||
offset = matchOffset = outerEnd;
|
||||
copyFromOffset = matchFromOffset = part.outerEnd;
|
||||
}
|
||||
if (!newText.isEmpty()) {
|
||||
newText.append(start + offset, len - offset);
|
||||
result.text = newText;
|
||||
}
|
||||
if (!newEntities.isEmpty()) {
|
||||
if (!newResult.empty()) {
|
||||
newResult.text.append(start + copyFromOffset, length - copyFromOffset);
|
||||
for (; existingEntityIndex < existingEntitiesCount; ++existingEntityIndex) {
|
||||
auto &entity = result.entities[existingEntityIndex];
|
||||
newEntities.push_back(entity);
|
||||
newEntities.back().shiftLeft(existingEntityShiftLeft);
|
||||
newResult.entities.push_back(entity);
|
||||
newResult.entities.back().shiftLeft(existingEntityShiftLeft);
|
||||
}
|
||||
result.entities = newEntities;
|
||||
newEntities = EntitiesInText();
|
||||
result = std::move(newResult);
|
||||
}
|
||||
}
|
||||
|
||||
// Some code is duplicated in flattextarea.cpp!
|
||||
void ParseEntities(TextWithEntities &result, int32 flags, bool rich) {
|
||||
if (flags & TextParseMarkdown) { // parse markdown entities (bold, italic, code and pre)
|
||||
ParseMarkdown(result, rich);
|
||||
}
|
||||
|
||||
auto newEntities = EntitiesInText();
|
||||
bool withHashtags = (flags & TextParseHashtags);
|
||||
bool withMentions = (flags & TextParseMentions);
|
||||
bool withBotCommands = (flags & TextParseBotCommands);
|
||||
|
||||
int existingEntityIndex = 0, existingEntitiesCount = result.entities.size();
|
||||
int existingEntityEnd = 0;
|
||||
|
||||
|
|
|
@ -115,6 +115,10 @@ private:
|
|||
struct TextWithEntities {
|
||||
QString text;
|
||||
EntitiesInText entities;
|
||||
|
||||
bool empty() const {
|
||||
return text.isEmpty();
|
||||
}
|
||||
};
|
||||
|
||||
enum {
|
||||
|
@ -124,7 +128,7 @@ enum {
|
|||
TextParseMentions = 0x008,
|
||||
TextParseHashtags = 0x010,
|
||||
TextParseBotCommands = 0x020,
|
||||
TextParseMono = 0x040,
|
||||
TextParseMarkdown = 0x040,
|
||||
|
||||
TextTwitterMentions = 0x100,
|
||||
TextTwitterHashtags = 0x200,
|
||||
|
@ -145,8 +149,10 @@ const QRegularExpression &RegExpMailNameAtEnd();
|
|||
const QRegularExpression &RegExpHashtag();
|
||||
const QRegularExpression &RegExpMention();
|
||||
const QRegularExpression &RegExpBotCommand();
|
||||
const QRegularExpression &RegExpMonoInline();
|
||||
const QRegularExpression &RegExpMonoBlock();
|
||||
const QRegularExpression &RegExpMarkdownBold();
|
||||
const QRegularExpression &RegExpMarkdownItalic();
|
||||
const QRegularExpression &RegExpMarkdownMonoInline();
|
||||
const QRegularExpression &RegExpMarkdownMonoBlock();
|
||||
|
||||
inline void Append(TextWithEntities &to, TextWithEntities &&append) {
|
||||
auto entitiesShiftRight = to.text.size();
|
||||
|
@ -192,10 +198,10 @@ enum class ConvertOption {
|
|||
WithLocal,
|
||||
SkipLocal,
|
||||
};
|
||||
MTPVector<MTPMessageEntity> EntitiesToMTP(const EntitiesInText &links, ConvertOption option = ConvertOption::WithLocal);
|
||||
MTPVector<MTPMessageEntity> EntitiesToMTP(const EntitiesInText &entities, ConvertOption option = ConvertOption::WithLocal);
|
||||
|
||||
// New entities are added to the ones that are already in result.
|
||||
// Changes text if (flags & TextParseMono).
|
||||
// Changes text if (flags & TextParseMarkdown).
|
||||
void ParseEntities(TextWithEntities &result, int32 flags, bool rich = false);
|
||||
QString ApplyEntities(const TextWithEntities &text);
|
||||
|
||||
|
|
|
@ -35,7 +35,7 @@ TextParseOptions _labelOptions = {
|
|||
};
|
||||
|
||||
TextParseOptions _labelMarkedOptions = {
|
||||
TextParseMultiline | TextParseRichText | TextParseLinks | TextParseHashtags | TextParseMentions | TextParseBotCommands | TextParseMono, // flags
|
||||
TextParseMultiline | TextParseRichText | TextParseLinks | TextParseHashtags | TextParseMentions | TextParseBotCommands | TextParseMarkdown, // flags
|
||||
0, // maxw
|
||||
0, // maxh
|
||||
Qt::LayoutDirectionAuto, // dir
|
||||
|
|
Loading…
Add table
Reference in a new issue