Apply markdown bold/italic when editing a message.

This commit is contained in:
John Preston 2017-07-06 18:19:50 +03:00
parent 148c04fb41
commit 921c27c9b1
1 changed files with 87 additions and 43 deletions

View File

@ -1959,21 +1959,39 @@ QString ApplyEntities(const TextWithEntities &text) {
if (text.entities.isEmpty()) return text.text; if (text.entities.isEmpty()) return text.text;
QMultiMap<int32, QString> closingTags; QMultiMap<int32, QString> closingTags;
QString code(qsl("`")), pre(qsl("```")); QMap<EntityInTextType, QString> tags;
tags.insert(EntityInTextCode, qsl("`"));
tags.insert(EntityInTextPre, qsl("```"));
tags.insert(EntityInTextBold, qsl("**"));
tags.insert(EntityInTextItalic, qsl("__"));
constexpr auto kLargestOpenCloseLength = 6;
QString result; QString result;
int32 size = text.text.size(); int32 size = text.text.size();
const QChar *b = text.text.constData(), *already = b, *e = b + size; const QChar *b = text.text.constData(), *already = b, *e = b + size;
auto entity = text.entities.cbegin(), end = text.entities.cend(); auto entity = text.entities.cbegin(), end = text.entities.cend();
while (entity != end && ((entity->type() != EntityInTextCode && entity->type() != EntityInTextPre) || entity->length() <= 0 || entity->offset() >= size)) { auto skipTillRelevantAndGetTag = [&entity, &end, size, &tags] {
++entity; while (entity != end) {
} if (entity->length() <= 0 || entity->offset() >= size) {
++entity;
continue;
}
auto it = tags.constFind(entity->type());
if (it == tags.cend()) {
++entity;
continue;
}
return it.value();
}
return QString();
};
auto tag = skipTillRelevantAndGetTag();
while (entity != end || !closingTags.isEmpty()) { while (entity != end || !closingTags.isEmpty()) {
int32 nextOpenEntity = (entity == end) ? (size + 1) : entity->offset(); auto nextOpenEntity = (entity == end) ? (size + 1) : entity->offset();
int32 nextCloseEntity = closingTags.isEmpty() ? (size + 1) : closingTags.cbegin().key(); auto nextCloseEntity = closingTags.isEmpty() ? (size + 1) : closingTags.cbegin().key();
if (nextOpenEntity <= nextCloseEntity) { if (nextOpenEntity <= nextCloseEntity) {
QString tag = (entity->type() == EntityInTextCode) ? code : pre; if (result.isEmpty()) result.reserve(text.text.size() + text.entities.size() * kLargestOpenCloseLength);
if (result.isEmpty()) result.reserve(text.text.size() + text.entities.size() * pre.size() * 2);
const QChar *offset = b + nextOpenEntity; const QChar *offset = b + nextOpenEntity;
if (offset > already) { if (offset > already) {
@ -1984,9 +2002,7 @@ QString ApplyEntities(const TextWithEntities &text) {
closingTags.insert(qMin(entity->offset() + entity->length(), size), tag); closingTags.insert(qMin(entity->offset() + entity->length(), size), tag);
++entity; ++entity;
while (entity != end && ((entity->type() != EntityInTextCode && entity->type() != EntityInTextPre) || entity->length() <= 0 || entity->offset() >= size)) { tag = skipTillRelevantAndGetTag();
++entity;
}
} else { } else {
const QChar *offset = b + nextCloseEntity; const QChar *offset = b + nextCloseEntity;
if (offset > already) { if (offset > already) {
@ -2007,33 +2023,40 @@ QString ApplyEntities(const TextWithEntities &text) {
return result; return result;
} }
void moveStringPart(QChar *start, int32 &to, int32 &from, int32 count, EntitiesInText *inOutEntities) { void MoveStringPart(TextWithEntities &result, int to, int from, int count) {
if (count > 0) { if (!count) return;
if (to < from) { if (to != from) {
memmove(start + to, start + from, count * sizeof(QChar)); auto start = result.text.data();
for (auto &entity : *inOutEntities) { memmove(start + to, start + from, count * sizeof(QChar));
if (entity.offset() >= from + count) break;
if (entity.offset() + entity.length() < from) continue; for (auto &entity : result.entities) {
if (entity.offset() >= from) { if (entity.offset() >= from + count) break;
entity.extendToLeft(from - to); if (entity.offset() + entity.length() <= from) continue;
} if (entity.offset() >= from) {
if (entity.offset() + entity.length() < from + count) { entity.extendToLeft(from - to);
entity.shrinkFromRight(from - to); }
} if (entity.offset() + entity.length() <= from + count) {
entity.shrinkFromRight(from - to);
} }
} }
to += count;
from += count;
} }
} }
void replaceStringWithEntities(const QLatin1String &from, QChar to, TextWithEntities &result, bool checkSpace = false) { void MovePartAndGoForward(TextWithEntities &result, int &to, int &from, int count) {
if (!count) return;
MoveStringPart(result, to, from, count);
to += count;
from += count;
}
void ReplaceStringWithChar(const QLatin1String &from, QChar to, TextWithEntities &result, bool checkSpace = false) {
Expects(from.size() > 1);
auto len = from.size(), s = result.text.size(), offset = 0, length = 0; auto len = from.size(), s = result.text.size(), offset = 0, length = 0;
auto i = result.entities.begin(), e = result.entities.end(); auto i = result.entities.begin(), e = result.entities.end();
for (QChar *start = result.text.data(); offset < s;) { for (auto start = result.text.data(); offset < s;) {
int32 nextOffset = result.text.indexOf(from, offset); auto nextOffset = result.text.indexOf(from, offset);
if (nextOffset < 0) { if (nextOffset < 0) {
moveStringPart(start, length, offset, s - offset, &result.entities); MovePartAndGoForward(result, length, offset, s - offset);
break; break;
} }
@ -2041,12 +2064,12 @@ void replaceStringWithEntities(const QLatin1String &from, QChar to, TextWithEnti
bool spaceBefore = (nextOffset > 0) && (start + nextOffset - 1)->isSpace(); bool spaceBefore = (nextOffset > 0) && (start + nextOffset - 1)->isSpace();
bool spaceAfter = (nextOffset + len < s) && (start + nextOffset + len)->isSpace(); bool spaceAfter = (nextOffset + len < s) && (start + nextOffset + len)->isSpace();
if (!spaceBefore && !spaceAfter) { if (!spaceBefore && !spaceAfter) {
moveStringPart(start, length, offset, nextOffset - offset + len + 1, &result.entities); MovePartAndGoForward(result, length, offset, nextOffset - offset + len + 1);
continue; continue;
} }
} }
bool skip = false; auto skip = false;
for (; i != e; ++i) { // find and check next finishing entity for (; i != e; ++i) { // find and check next finishing entity
if (i->offset() + i->length() > nextOffset) { if (i->offset() + i->length() > nextOffset) {
skip = (i->offset() < nextOffset + len); skip = (i->offset() < nextOffset + len);
@ -2054,11 +2077,11 @@ void replaceStringWithEntities(const QLatin1String &from, QChar to, TextWithEnti
} }
} }
if (skip) { if (skip) {
moveStringPart(start, length, offset, nextOffset - offset + len, &result.entities); MovePartAndGoForward(result, length, offset, nextOffset - offset + len);
continue; continue;
} }
moveStringPart(start, length, offset, nextOffset - offset, &result.entities); MovePartAndGoForward(result, length, offset, nextOffset - offset);
*(start + length) = to; *(start + length) = to;
++length; ++length;
@ -2074,9 +2097,9 @@ void PrepareForSending(TextWithEntities &result, int32 flags) {
ParseEntities(result, flags); ParseEntities(result, flags);
} }
replaceStringWithEntities(qstr("--"), QChar(8212), result, true); ReplaceStringWithChar(qstr("--"), QChar(8212), result, true);
replaceStringWithEntities(qstr("<<"), QChar(171), result); ReplaceStringWithChar(qstr("<<"), QChar(171), result);
replaceStringWithEntities(qstr(">>"), QChar(187), result); ReplaceStringWithChar(qstr(">>"), QChar(187), result);
if (cReplaceEmojis()) { if (cReplaceEmojis()) {
Ui::Emoji::ReplaceInText(result); Ui::Emoji::ReplaceInText(result);
@ -2087,18 +2110,39 @@ void PrepareForSending(TextWithEntities &result, int32 flags) {
// Replace bad symbols with space and remove '\r'. // Replace bad symbols with space and remove '\r'.
void ApplyServerCleaning(TextWithEntities &result) { void ApplyServerCleaning(TextWithEntities &result) {
result.text = result.text.replace('\t', qstr(" ")); // TODO WTF? modify entities! auto len = result.text.size();
int32 len = result.text.size(), to = 0, from = 0;
QChar *start = result.text.data(); // Replace tabs with two spaces.
for (QChar *ch = start, *end = start + len; ch < end; ++ch) { if (auto tabs = std::count(result.text.cbegin(), result.text.cend(), '\t')) {
auto replacement = qsl(" ");
auto replacementLength = replacement.size();
auto shift = (replacementLength - 1);
result.text.resize(len + shift * tabs);
for (auto i = len, movedTill = len, to = result.text.size(); i > 0; --i) {
if (result.text[i - 1] == '\t') {
auto toMove = movedTill - i;
to -= toMove;
MoveStringPart(result, to, i, toMove);
to -= replacementLength;
memcpy(result.text.data() + to, replacement.constData(), replacementLength * sizeof(QChar));
movedTill = i - 1;
}
}
len = result.text.size();
}
auto to = 0;
auto from = 0;
auto start = result.text.data();
for (auto ch = start, end = start + len; ch < end; ++ch) {
if (ch->unicode() == '\r') { if (ch->unicode() == '\r') {
moveStringPart(start, to, from, (ch - start) - from, &result.entities); MovePartAndGoForward(result, to, from, (ch - start) - from);
++from; ++from;
} else if (chReplacedBySpace(*ch)) { } else if (chReplacedBySpace(*ch)) {
*ch = ' '; *ch = ' ';
} }
} }
moveStringPart(start, to, from, len - from, &result.entities); MovePartAndGoForward(result, to, from, len - from);
if (to < len) result.text.resize(to); if (to < len) result.text.resize(to);
} }