mirror of https://github.com/procxx/kepka.git
Parse bold / italic markdown entities.
This commit is contained in:
parent
da0d78135d
commit
eaf91bba58
|
@ -386,7 +386,7 @@ void InnerWidget::clearAndRequestLog() {
|
||||||
|
|
||||||
void InnerWidget::updateEmptyText() {
|
void InnerWidget::updateEmptyText() {
|
||||||
auto options = _defaultOptions;
|
auto options = _defaultOptions;
|
||||||
options.flags |= TextParseMono; // For bold :/
|
options.flags |= TextParseMarkdown;
|
||||||
auto hasSearch = !_searchQuery.isEmpty();
|
auto hasSearch = !_searchQuery.isEmpty();
|
||||||
auto hasFilter = (_filter.flags != 0) || !_filter.allUsers;
|
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) };
|
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
|
Qt::LayoutDirectionAuto, // dir
|
||||||
};
|
};
|
||||||
TextParseOptions _webpageDescriptionOptions = {
|
TextParseOptions _webpageDescriptionOptions = {
|
||||||
TextParseLinks | TextParseMentions | TextParseHashtags | TextParseMultiline | TextParseRichText | TextParseMono, // flags
|
TextParseLinks | TextParseMentions | TextParseHashtags | TextParseMultiline | TextParseRichText | TextParseMarkdown, // flags
|
||||||
0, // maxw
|
0, // maxw
|
||||||
0, // maxh
|
0, // maxh
|
||||||
Qt::LayoutDirectionAuto, // dir
|
Qt::LayoutDirectionAuto, // dir
|
||||||
|
|
|
@ -44,13 +44,13 @@ TextParseOptions _textDlgOptions = {
|
||||||
Qt::LayoutDirectionAuto, // lang-dependent
|
Qt::LayoutDirectionAuto, // lang-dependent
|
||||||
};
|
};
|
||||||
TextParseOptions _historyTextOptions = {
|
TextParseOptions _historyTextOptions = {
|
||||||
TextParseLinks | TextParseMentions | TextParseHashtags | TextParseMultiline | TextParseRichText | TextParseMono, // flags
|
TextParseLinks | TextParseMentions | TextParseHashtags | TextParseMultiline | TextParseRichText | TextParseMarkdown, // flags
|
||||||
0, // maxw
|
0, // maxw
|
||||||
0, // maxh
|
0, // maxh
|
||||||
Qt::LayoutDirectionAuto, // dir
|
Qt::LayoutDirectionAuto, // dir
|
||||||
};
|
};
|
||||||
TextParseOptions _historyBotOptions = {
|
TextParseOptions _historyBotOptions = {
|
||||||
TextParseLinks | TextParseMentions | TextParseHashtags | TextParseBotCommands | TextParseMultiline | TextParseRichText | TextParseMono, // flags
|
TextParseLinks | TextParseMentions | TextParseHashtags | TextParseBotCommands | TextParseMultiline | TextParseRichText | TextParseMarkdown, // flags
|
||||||
0, // maxw
|
0, // maxw
|
||||||
0, // maxh
|
0, // maxh
|
||||||
Qt::LayoutDirectionAuto, // dir
|
Qt::LayoutDirectionAuto, // dir
|
||||||
|
|
|
@ -41,7 +41,7 @@ namespace Layout {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
TextParseOptions _documentNameOptions = {
|
TextParseOptions _documentNameOptions = {
|
||||||
TextParseMultiline | TextParseRichText | TextParseLinks | TextParseHashtags | TextParseMentions | TextParseBotCommands | TextParseMono, // flags
|
TextParseMultiline | TextParseRichText | TextParseLinks | TextParseHashtags | TextParseMentions | TextParseBotCommands | TextParseMarkdown, // flags
|
||||||
0, // maxw
|
0, // maxw
|
||||||
0, // maxh
|
0, // maxh
|
||||||
Qt::LayoutDirectionAuto, // dir
|
Qt::LayoutDirectionAuto, // dir
|
||||||
|
|
|
@ -513,8 +513,8 @@ public:
|
||||||
bool parseMentions = (options.flags & TextParseMentions);
|
bool parseMentions = (options.flags & TextParseMentions);
|
||||||
bool parseHashtags = (options.flags & TextParseHashtags);
|
bool parseHashtags = (options.flags & TextParseHashtags);
|
||||||
bool parseBotCommands = (options.flags & TextParseBotCommands);
|
bool parseBotCommands = (options.flags & TextParseBotCommands);
|
||||||
bool parseMono = (options.flags & TextParseMono);
|
bool parseMarkdown = (options.flags & TextParseMarkdown);
|
||||||
if (!parseMentions || !parseHashtags || !parseBotCommands || !parseMono) {
|
if (!parseMentions || !parseHashtags || !parseBotCommands || !parseMarkdown) {
|
||||||
int32 i = 0, l = preparsed.size();
|
int32 i = 0, l = preparsed.size();
|
||||||
source.entities.clear();
|
source.entities.clear();
|
||||||
source.entities.reserve(l);
|
source.entities.reserve(l);
|
||||||
|
@ -524,7 +524,7 @@ public:
|
||||||
if (((type == EntityInTextMention || type == EntityInTextMentionName) && !parseMentions) ||
|
if (((type == EntityInTextMention || type == EntityInTextMentionName) && !parseMentions) ||
|
||||||
(type == EntityInTextHashtag && !parseHashtags) ||
|
(type == EntityInTextHashtag && !parseHashtags) ||
|
||||||
(type == EntityInTextBotCommand && !parseBotCommands) ||
|
(type == EntityInTextBotCommand && !parseBotCommands) ||
|
||||||
((type == EntityInTextBold || type == EntityInTextItalic || type == EntityInTextCode || type == EntityInTextPre) && !parseMono)) {
|
((type == EntityInTextBold || type == EntityInTextItalic || type == EntityInTextCode || type == EntityInTextPre) && !parseMarkdown)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
source.entities.push_back(preparsed.at(i));
|
source.entities.push_back(preparsed.at(i));
|
||||||
|
|
|
@ -43,24 +43,40 @@ QString ExpressionMailNameAtEnd() {
|
||||||
return qsl("[a-zA-Z\\-_\\.0-9]{1,256}$");
|
return qsl("[a-zA-Z\\-_\\.0-9]{1,256}$");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString ExpressionSeparators(const QString &additional) {
|
||||||
|
return qsl("\\s\\.,:;<>|'\"\\[\\]\\{\\}\\~\\!\\%\\^\\(\\)\\-\\+=\\x10") + additional;
|
||||||
|
}
|
||||||
|
|
||||||
QString ExpressionHashtag() {
|
QString ExpressionHashtag() {
|
||||||
return qsl("(^|[\\s\\.,:;<>|'\"\\[\\]\\{\\}`\\~\\!\\%\\^\\*\\(\\)\\-\\+=\\x10])#[\\w]{2,64}([\\W]|$)");
|
return qsl("(^|[") + ExpressionSeparators(qsl("`\\*/")) + qsl("])#[\\w]{2,64}([\\W]|$)");
|
||||||
}
|
}
|
||||||
|
|
||||||
QString ExpressionMention() {
|
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() {
|
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
|
QString ExpressionMarkdownBold() {
|
||||||
return qsl("(^|[\\s\\.,:;<>|'\"\\[\\]\\{\\}`\\~\\!\\?\\%\\^\\*\\(\\)\\-\\+=\\x10])(````?)[\\s\\S]+?(````?)([\\s\\.,:;<>|'\"\\[\\]\\{\\}`\\~\\!\\?\\%\\^\\*\\(\\)\\-\\+=\\x10]|$)");
|
auto separators = ExpressionSeparators(qsl("`/"));
|
||||||
|
return qsl("(^|[") + separators + qsl("])(\\*\\*)[\\s\\S]+?(\\*\\*)([") + separators + qsl("]|$)");
|
||||||
}
|
}
|
||||||
|
|
||||||
QString ExpressionMonoBlock() { // code
|
QString ExpressionMarkdownItalic() {
|
||||||
return qsl("(^|[\\s\\.,:;<>|'\"\\[\\]\\{\\}`\\~\\!\\?\\%\\^\\*\\(\\)\\-\\+=\\x10])(`)[^\\n]+?(`)([\\s\\.,:;<>|'\"\\[\\]\\{\\}`\\~\\!\\?\\%\\^\\*\\(\\)\\-\\+=\\x10]|$)");
|
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) {
|
QRegularExpression CreateRegExp(const QString &expression) {
|
||||||
|
@ -1146,13 +1162,23 @@ const QRegularExpression &RegExpBotCommand() {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
const QRegularExpression &RegExpMonoInline() {
|
const QRegularExpression &RegExpMarkdownBold() {
|
||||||
static const auto result = CreateRegExp(ExpressionMonoInline());
|
static const auto result = CreateRegExp(ExpressionMarkdownBold());
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
const QRegularExpression &RegExpMonoBlock() {
|
const QRegularExpression &RegExpMarkdownItalic() {
|
||||||
static const auto result = CreateRegExp(ExpressionMonoBlock());
|
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;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1471,22 +1497,25 @@ EntitiesInText EntitiesFromMTP(const QVector<MTPMessageEntity> &entities) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
MTPVector<MTPMessageEntity> EntitiesToMTP(const EntitiesInText &links, ConvertOption option) {
|
MTPVector<MTPMessageEntity> EntitiesToMTP(const EntitiesInText &entities, ConvertOption option) {
|
||||||
auto v = QVector<MTPMessageEntity>();
|
auto v = QVector<MTPMessageEntity>();
|
||||||
v.reserve(links.size());
|
v.reserve(entities.size());
|
||||||
for_const (auto &link, links) {
|
for_const (auto &entity, entities) {
|
||||||
if (link.length() <= 0) continue;
|
if (entity.length() <= 0) continue;
|
||||||
if (option == ConvertOption::SkipLocal
|
if (option == ConvertOption::SkipLocal
|
||||||
&& link.type() != EntityInTextCode
|
&& entity.type() != EntityInTextBold
|
||||||
&& link.type() != EntityInTextPre
|
&& entity.type() != EntityInTextItalic
|
||||||
&& link.type() != EntityInTextMentionName) {
|
&& entity.type() != EntityInTextCode
|
||||||
|
&& entity.type() != EntityInTextPre
|
||||||
|
&& entity.type() != EntityInTextMentionName) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto offset = MTP_int(link.offset()), length = MTP_int(link.length());
|
auto offset = MTP_int(entity.offset());
|
||||||
switch (link.type()) {
|
auto length = MTP_int(entity.length());
|
||||||
|
switch (entity.type()) {
|
||||||
case EntityInTextUrl: v.push_back(MTP_messageEntityUrl(offset, length)); break;
|
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 EntityInTextEmail: v.push_back(MTP_messageEntityEmail(offset, length)); break;
|
||||||
case EntityInTextHashtag: v.push_back(MTP_messageEntityHashtag(offset, length)); break;
|
case EntityInTextHashtag: v.push_back(MTP_messageEntityHashtag(offset, length)); break;
|
||||||
case EntityInTextMention: v.push_back(MTP_messageEntityMention(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_inputUser(MTP_int(fields.userId), MTP_long(fields.accessHash));
|
||||||
}
|
}
|
||||||
return MTP_inputUserEmpty();
|
return MTP_inputUserEmpty();
|
||||||
})(link.data());
|
})(entity.data());
|
||||||
if (inputUser.type() != mtpc_inputUserEmpty) {
|
if (inputUser.type() != mtpc_inputUserEmpty) {
|
||||||
v.push_back(MTP_inputMessageEntityMentionName(offset, length, inputUser));
|
v.push_back(MTP_inputMessageEntityMentionName(offset, length, inputUser));
|
||||||
}
|
}
|
||||||
|
@ -1508,172 +1537,225 @@ MTPVector<MTPMessageEntity> EntitiesToMTP(const EntitiesInText &links, ConvertOp
|
||||||
case EntityInTextBold: v.push_back(MTP_messageEntityBold(offset, length)); break;
|
case EntityInTextBold: v.push_back(MTP_messageEntityBold(offset, length)); break;
|
||||||
case EntityInTextItalic: v.push_back(MTP_messageEntityItalic(offset, length)); break;
|
case EntityInTextItalic: v.push_back(MTP_messageEntityItalic(offset, length)); break;
|
||||||
case EntityInTextCode: v.push_back(MTP_messageEntityCode(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));
|
return MTP_vector<MTPMessageEntity>(std::move(v));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Some code is duplicated in flattextarea.cpp!
|
struct MarkdownPart {
|
||||||
void ParseEntities(TextWithEntities &result, int32 flags, bool rich) {
|
EntityInTextType type = EntityInTextInvalid;
|
||||||
auto newEntities = EntitiesInText();
|
int outerStart = 0;
|
||||||
|
int innerStart = 0;
|
||||||
|
int innerEnd = 0;
|
||||||
|
int outerEnd = 0;
|
||||||
|
bool addNewlineBefore = false;
|
||||||
|
bool addNewlineAfter = false;
|
||||||
|
};
|
||||||
|
|
||||||
bool withHashtags = (flags & TextParseHashtags);
|
MarkdownPart GetMarkdownPart(EntityInTextType type, const QString &text, int matchFromOffset, bool rich) {
|
||||||
bool withMentions = (flags & TextParseMentions);
|
auto result = MarkdownPart();
|
||||||
bool withBotCommands = (flags & TextParseBotCommands);
|
auto regexp = [type] {
|
||||||
bool withMono = (flags & TextParseMono);
|
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)
|
auto match = regexp().match(text, matchFromOffset);
|
||||||
int existingEntityIndex = 0, existingEntitiesCount = result.entities.size();
|
if (!match.hasMatch()) {
|
||||||
int existingEntityShiftLeft = 0;
|
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;
|
void AdjustMarkdownPrePart(MarkdownPart &result, const TextWithEntities &text, bool rich) {
|
||||||
bool inLink = false, commandIsLink = false;
|
auto start = text.text.constData();
|
||||||
const QChar *start = result.text.constData();
|
auto length = text.text.size();
|
||||||
for (; matchOffset < len;) {
|
auto lastEntityBeforeEnd = 0;
|
||||||
if (commandOffset <= matchOffset) {
|
auto firstEntityInsideStart = result.innerEnd;
|
||||||
for (commandOffset = matchOffset; commandOffset < len; ++commandOffset) {
|
auto lastEntityInsideEnd = result.innerStart;
|
||||||
if (*(start + commandOffset) == TextCommand) {
|
auto firstEntityAfterStart = length;
|
||||||
inLink = commandIsLink;
|
for_const (auto &entity, text.entities) {
|
||||||
commandIsLink = textcmdStartsLink(start, len, commandOffset);
|
if (entity.offset() < result.outerStart) {
|
||||||
break;
|
lastEntityBeforeEnd = entity.offset() + entity.length();
|
||||||
}
|
} else if (entity.offset() >= result.outerEnd) {
|
||||||
}
|
firstEntityAfterStart = entity.offset();
|
||||||
if (commandOffset >= len) {
|
break;
|
||||||
|
} else if (entity.offset() >= result.innerStart) {
|
||||||
|
accumulate_min(firstEntityInsideStart, entity.offset());
|
||||||
|
lastEntityInsideEnd = entity.offset() + entity.length();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while (result.outerStart > lastEntityBeforeEnd
|
||||||
|
&& chIsSpace(*(start + result.outerStart - 1), rich)
|
||||||
|
&& !chIsNewline(*(start + result.outerStart - 1))) {
|
||||||
|
--result.outerStart;
|
||||||
|
}
|
||||||
|
result.addNewlineBefore = (result.outerStart > 0 && !chIsNewline(*(start + result.outerStart - 1)));
|
||||||
|
|
||||||
|
for (auto testInnerStart = result.innerStart; testInnerStart < firstEntityInsideStart; ++testInnerStart) {
|
||||||
|
if (chIsNewline(*(start + testInnerStart))) {
|
||||||
|
result.innerStart = testInnerStart + 1;
|
||||||
|
break;
|
||||||
|
} else if (!chIsSpace(*(start + testInnerStart))) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (auto testInnerEnd = result.innerEnd; lastEntityInsideEnd < testInnerEnd;) {
|
||||||
|
--testInnerEnd;
|
||||||
|
if (chIsNewline(*(start + testInnerEnd))) {
|
||||||
|
result.innerEnd = testInnerEnd;
|
||||||
|
break;
|
||||||
|
} else if (!chIsSpace(*(start + testInnerEnd))) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
while (result.outerEnd < firstEntityAfterStart
|
||||||
|
&& chIsSpace(*(start + result.outerEnd))
|
||||||
|
&& !chIsNewline(*(start + result.outerEnd))) {
|
||||||
|
++result.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;
|
inLink = commandIsLink;
|
||||||
commandIsLink = false;
|
commandIsLink = textcmdStartsLink(start, length, nextCommandOffset);
|
||||||
}
|
|
||||||
}
|
|
||||||
auto mPre = RegExpMonoInline().match(result.text, matchOffset);
|
|
||||||
auto mCode = RegExpMonoBlock().match(result.text, matchOffset);
|
|
||||||
if (!mPre.hasMatch() && !mCode.hasMatch()) break;
|
|
||||||
|
|
||||||
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;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (intersectedEntityEnd > 0) {
|
if (nextCommandOffset >= length) {
|
||||||
matchOffset = qMax(innerStart, intersectedEntityEnd);
|
inLink = commandIsLink;
|
||||||
continue;
|
commandIsLink = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
|
||||||
if (chIsNewline(*(start + testInnerStart))) {
|
|
||||||
innerStart = testInnerStart + 1;
|
|
||||||
break;
|
|
||||||
} else if (!chIsSpace(*(start + testInnerStart))) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (int32 testInnerEnd = innerEnd; innerStart < testInnerEnd;) {
|
|
||||||
--testInnerEnd;
|
|
||||||
if (chIsNewline(*(start + testInnerEnd))) {
|
|
||||||
innerEnd = testInnerEnd;
|
|
||||||
break;
|
|
||||||
} else if (!chIsSpace(*(start + testInnerEnd))) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
while (outerEnd < len && chIsSpace(*(start + outerEnd)) && !chIsNewline(*(start + outerEnd))) {
|
|
||||||
++outerEnd;
|
|
||||||
}
|
|
||||||
addNewlineAfter = (outerEnd < len && !chIsNewline(*(start + outerEnd)));
|
|
||||||
}
|
|
||||||
|
|
||||||
for (; existingEntityIndex < existingEntitiesCount && result.entities[existingEntityIndex].offset() < innerStart; ++existingEntityIndex) {
|
|
||||||
auto &entity = result.entities[existingEntityIndex];
|
|
||||||
newEntities.push_back(entity);
|
|
||||||
newEntities.back().shiftLeft(existingEntityShiftLeft);
|
|
||||||
}
|
|
||||||
if (outerStart > offset) newText.append(start + offset, outerStart - offset);
|
|
||||||
if (addNewlineBefore) newText.append('\n');
|
|
||||||
existingEntityShiftLeft += (innerStart - outerStart) - (addNewlineBefore ? 1 : 0);
|
|
||||||
|
|
||||||
int entityStart = newText.size(), entityLength = innerEnd - innerStart;
|
|
||||||
newEntities.push_back(EntityInText(pre ? EntityInTextPre : EntityInTextCode, entityStart, entityLength));
|
|
||||||
|
|
||||||
for (; existingEntityIndex < existingEntitiesCount && result.entities[existingEntityIndex].offset() <= innerEnd; ++existingEntityIndex) {
|
|
||||||
auto &entity = result.entities[existingEntityIndex];
|
|
||||||
newEntities.push_back(entity);
|
|
||||||
newEntities.back().shiftLeft(existingEntityShiftLeft);
|
|
||||||
}
|
|
||||||
newText.append(start + innerStart, entityLength);
|
|
||||||
if (addNewlineAfter) newText.append('\n');
|
|
||||||
existingEntityShiftLeft += (outerEnd - innerEnd) - (addNewlineAfter ? 1 : 0);
|
|
||||||
|
|
||||||
offset = matchOffset = outerEnd;
|
|
||||||
}
|
}
|
||||||
if (!newText.isEmpty()) {
|
auto part = MarkdownPart();
|
||||||
newText.append(start + offset, len - offset);
|
auto testPart = [&part, &result, matchFromOffset, rich](EntityInTextType type) {
|
||||||
result.text = newText;
|
auto test = GetMarkdownPart(type, result.text, matchFromOffset, rich);
|
||||||
}
|
if (test.type != EntityInTextInvalid) {
|
||||||
if (!newEntities.isEmpty()) {
|
if (part.type == EntityInTextInvalid || part.outerStart > test.outerStart) {
|
||||||
for (; existingEntityIndex < existingEntitiesCount; ++existingEntityIndex) {
|
part = test;
|
||||||
auto &entity = result.entities[existingEntityIndex];
|
}
|
||||||
newEntities.push_back(entity);
|
|
||||||
newEntities.back().shiftLeft(existingEntityShiftLeft);
|
|
||||||
}
|
}
|
||||||
result.entities = newEntities;
|
};
|
||||||
newEntities = EntitiesInText();
|
testPart(EntityInTextBold);
|
||||||
|
testPart(EntityInTextItalic);
|
||||||
|
testPart(EntityInTextPre);
|
||||||
|
testPart(EntityInTextCode);
|
||||||
|
if (part.type == EntityInTextInvalid) {
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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];
|
||||||
|
newResult.entities.push_back(entity);
|
||||||
|
newResult.entities.back().shiftLeft(existingEntityShiftLeft);
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
|
||||||
|
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() <= part.innerEnd; ++existingEntityIndex) {
|
||||||
|
auto &entity = result.entities[existingEntityIndex];
|
||||||
|
newResult.entities.push_back(entity);
|
||||||
|
newResult.entities.back().shiftLeft(existingEntityShiftLeft);
|
||||||
|
}
|
||||||
|
newResult.text.append(start + part.innerStart, entityLength);
|
||||||
|
if (part.addNewlineAfter) newResult.text.append('\n');
|
||||||
|
existingEntityShiftLeft += (part.outerEnd - part.innerEnd) - (part.addNewlineAfter ? 1 : 0);
|
||||||
|
|
||||||
|
copyFromOffset = matchFromOffset = part.outerEnd;
|
||||||
}
|
}
|
||||||
|
if (!newResult.empty()) {
|
||||||
|
newResult.text.append(start + copyFromOffset, length - copyFromOffset);
|
||||||
|
for (; existingEntityIndex < existingEntitiesCount; ++existingEntityIndex) {
|
||||||
|
auto &entity = result.entities[existingEntityIndex];
|
||||||
|
newResult.entities.push_back(entity);
|
||||||
|
newResult.entities.back().shiftLeft(existingEntityShiftLeft);
|
||||||
|
}
|
||||||
|
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 existingEntityIndex = 0, existingEntitiesCount = result.entities.size();
|
||||||
int existingEntityEnd = 0;
|
int existingEntityEnd = 0;
|
||||||
|
|
|
@ -115,6 +115,10 @@ private:
|
||||||
struct TextWithEntities {
|
struct TextWithEntities {
|
||||||
QString text;
|
QString text;
|
||||||
EntitiesInText entities;
|
EntitiesInText entities;
|
||||||
|
|
||||||
|
bool empty() const {
|
||||||
|
return text.isEmpty();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
|
@ -124,7 +128,7 @@ enum {
|
||||||
TextParseMentions = 0x008,
|
TextParseMentions = 0x008,
|
||||||
TextParseHashtags = 0x010,
|
TextParseHashtags = 0x010,
|
||||||
TextParseBotCommands = 0x020,
|
TextParseBotCommands = 0x020,
|
||||||
TextParseMono = 0x040,
|
TextParseMarkdown = 0x040,
|
||||||
|
|
||||||
TextTwitterMentions = 0x100,
|
TextTwitterMentions = 0x100,
|
||||||
TextTwitterHashtags = 0x200,
|
TextTwitterHashtags = 0x200,
|
||||||
|
@ -145,8 +149,10 @@ const QRegularExpression &RegExpMailNameAtEnd();
|
||||||
const QRegularExpression &RegExpHashtag();
|
const QRegularExpression &RegExpHashtag();
|
||||||
const QRegularExpression &RegExpMention();
|
const QRegularExpression &RegExpMention();
|
||||||
const QRegularExpression &RegExpBotCommand();
|
const QRegularExpression &RegExpBotCommand();
|
||||||
const QRegularExpression &RegExpMonoInline();
|
const QRegularExpression &RegExpMarkdownBold();
|
||||||
const QRegularExpression &RegExpMonoBlock();
|
const QRegularExpression &RegExpMarkdownItalic();
|
||||||
|
const QRegularExpression &RegExpMarkdownMonoInline();
|
||||||
|
const QRegularExpression &RegExpMarkdownMonoBlock();
|
||||||
|
|
||||||
inline void Append(TextWithEntities &to, TextWithEntities &&append) {
|
inline void Append(TextWithEntities &to, TextWithEntities &&append) {
|
||||||
auto entitiesShiftRight = to.text.size();
|
auto entitiesShiftRight = to.text.size();
|
||||||
|
@ -192,10 +198,10 @@ enum class ConvertOption {
|
||||||
WithLocal,
|
WithLocal,
|
||||||
SkipLocal,
|
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.
|
// 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);
|
void ParseEntities(TextWithEntities &result, int32 flags, bool rich = false);
|
||||||
QString ApplyEntities(const TextWithEntities &text);
|
QString ApplyEntities(const TextWithEntities &text);
|
||||||
|
|
||||||
|
|
|
@ -35,7 +35,7 @@ TextParseOptions _labelOptions = {
|
||||||
};
|
};
|
||||||
|
|
||||||
TextParseOptions _labelMarkedOptions = {
|
TextParseOptions _labelMarkedOptions = {
|
||||||
TextParseMultiline | TextParseRichText | TextParseLinks | TextParseHashtags | TextParseMentions | TextParseBotCommands | TextParseMono, // flags
|
TextParseMultiline | TextParseRichText | TextParseLinks | TextParseHashtags | TextParseMentions | TextParseBotCommands | TextParseMarkdown, // flags
|
||||||
0, // maxw
|
0, // maxw
|
||||||
0, // maxh
|
0, // maxh
|
||||||
Qt::LayoutDirectionAuto, // dir
|
Qt::LayoutDirectionAuto, // dir
|
||||||
|
|
Loading…
Reference in New Issue