Apply markdown only when sending the message.

This commit is contained in:
John Preston 2018-05-31 21:28:37 +03:00
parent bfc748cd31
commit 43d19920e0
11 changed files with 360 additions and 174 deletions

View File

@ -464,7 +464,11 @@ void GroupInfoBox::submit() {
if (_creationRequestId) return;
auto title = TextUtilities::PrepareForSending(_title->getLastText());
auto description = _description ? TextUtilities::PrepareForSending(_description->getLastText(), TextUtilities::PrepareTextOption::CheckLinks) : QString();
auto description = _description
? TextUtilities::PrepareForSending(
_description->getLastText(),
TextUtilities::PrepareTextOption::CheckLinks)
: QString();
if (title.isEmpty()) {
_title->setFocus();
_title->showError();

View File

@ -345,7 +345,7 @@ void EditCaptionBox::save() {
if (_previewCancelled) {
flags |= MTPmessages_EditMessage::Flag::f_no_webpage;
}
const auto textWithTags = _field->getTextWithTags();
const auto textWithTags = _field->getTextWithAppliedMarkdown();
auto sending = TextWithEntities{
textWithTags.text,
ConvertTextTagsToEntities(textWithTags.tags)

View File

@ -1795,7 +1795,7 @@ void SendFilesBox::send(bool ctrlShiftEnter) {
_confirmed = true;
if (_confirmedCallback) {
auto caption = _caption
? _caption->getTextWithTags()
? _caption->getTextWithAppliedMarkdown()
: TextWithTags();
_confirmedCallback(
std::move(_list),

View File

@ -571,6 +571,7 @@ void MessageLinksParser::parse() {
const auto &textWithTags = _field->getTextWithTags();
const auto &text = textWithTags.text;
const auto &tags = textWithTags.tags;
const auto &markdownTags = _field->getMarkdownTags();
if (text.isEmpty()) {
_list = QStringList();
return;
@ -578,9 +579,8 @@ void MessageLinksParser::parse() {
auto ranges = QVector<LinkRange>();
const auto tagsBegin = tags.begin();
auto tag = tags.begin();
const auto tagsEnd = tags.end();
auto tag = tagsBegin;
const auto processTag = [&] {
Expects(tag != tagsEnd);
@ -605,6 +605,25 @@ void MessageLinksParser::parse() {
return true;
};
auto markdownTag = markdownTags.begin();
const auto markdownTagsEnd = markdownTags.end();
const auto markdownTagsAllow = [&](int from, int length) {
while (markdownTag != markdownTagsEnd
&& (markdownTag->start + markdownTag->length <= from
|| !markdownTag->closed)) {
++markdownTag;
continue;
}
if (markdownTag == markdownTagsEnd
|| markdownTag->start >= from + length) {
return true;
}
// Ignore http-links that are completely inside some tags.
// This will allow sending http://test.com/__test__/test correctly.
return (markdownTag->start > from
|| markdownTag->start + markdownTag->length < from + length);
};
const auto len = text.size();
const QChar *start = text.unicode(), *end = start + text.size();
for (auto offset = 0, matchOffset = offset; offset < len;) {
@ -671,7 +690,9 @@ void MessageLinksParser::parse() {
};
processTagsBefore(domainOffset);
if (!hasTagsIntersection(range.start + range.length)) {
ranges.push_back(range);
if (markdownTagsAllow(range.start, range.length)) {
ranges.push_back(range);
}
}
offset = matchOffset = p - start;
}

View File

@ -2891,10 +2891,14 @@ void HistoryWidget::showNextUnreadMention() {
void HistoryWidget::saveEditMsg() {
if (_saveEditMsgRequestId) return;
WebPageId webPageId = _previewCancelled ? CancelledWebPageId : ((_previewData && _previewData->pendingTill >= 0) ? _previewData->id : 0);
const auto webPageId = _previewCancelled
? CancelledWebPageId
: ((_previewData && _previewData->pendingTill >= 0)
? _previewData->id
: WebPageId(0));
auto &textWithTags = _field->getTextWithTags();
auto prepareFlags = Ui::ItemTextOptions(_history, App::self()).flags;
const auto textWithTags = _field->getTextWithAppliedMarkdown();
const auto prepareFlags = Ui::ItemTextOptions(_history, App::self()).flags;
auto sending = TextWithEntities();
auto left = TextWithEntities { textWithTags.text, ConvertTextTagsToEntities(textWithTags.tags) };
TextUtilities::PrepareForSending(left, prepareFlags);
@ -3000,7 +3004,7 @@ void HistoryWidget::send() {
WebPageId webPageId = _previewCancelled ? CancelledWebPageId : ((_previewData && _previewData->pendingTill >= 0) ? _previewData->id : 0);
auto message = MainWidget::MessageToSend(_history);
message.textWithTags = _field->getTextWithTags();
message.textWithTags = _field->getTextWithAppliedMarkdown();
message.replyTo = replyToId();
message.webPageId = webPageId;
App::main()->sendMessage(message);

View File

@ -1249,7 +1249,10 @@ void MainWidget::sendMessage(const MessageToSend &message) {
saveRecentHashtags(textWithTags.text);
auto sending = TextWithEntities();
auto left = TextWithEntities { textWithTags.text, ConvertTextTagsToEntities(textWithTags.tags) };
auto left = TextWithEntities {
textWithTags.text,
ConvertTextTagsToEntities(textWithTags.tags)
};
auto prepareFlags = Ui::ItemTextOptions(history, App::self()).flags;
TextUtilities::PrepareForSending(left, prepareFlags);

View File

@ -1579,7 +1579,7 @@ TextWithEntities ParseEntities(const QString &text, int32 flags) {
return result;
}
// Some code is duplicated in flattextarea.cpp!
// Some code is duplicated in message_field.cpp!
void ParseEntities(TextWithEntities &result, int32 flags, bool rich) {
constexpr auto kNotFound = std::numeric_limits<int>::max();

View File

@ -126,7 +126,8 @@ enum {
struct TextWithTags {
struct Tag {
int offset, length;
int offset = 0;
int length = 0;
QString id;
};
using Tags = QVector<Tag>;

View File

@ -128,17 +128,99 @@ struct TagStartExpression {
QString badAfter;
};
struct TagStartItem {
int offset = 0;
int position = -1;
};
constexpr auto kTagBoldIndex = 0;
constexpr auto kTagItalicIndex = 1;
constexpr auto kTagCodeIndex = 2;
constexpr auto kTagPreIndex = 3;
constexpr auto kInvalidPosition = std::numeric_limits<int>::max() / 2;
class TagSearchItem {
public:
enum class Edge {
Open,
Close,
};
int matchPosition(Edge edge) const {
return (_position >= 0) ? _position : kInvalidPosition;
}
void applyOffset(int offset) {
if (_position < offset) {
_position = -1;
}
accumulate_max(_offset, offset);
}
void fill(
const QString &text,
Edge edge,
const TagStartExpression &expression) {
const auto length = text.size();
const auto &tag = expression.tag;
const auto tagLength = tag.size();
const auto isGood = [&](QChar ch) {
return (expression.goodBefore.indexOf(ch) >= 0);
};
const auto isBad = [&](QChar ch) {
return (expression.badAfter.indexOf(ch) >= 0);
};
const auto check = [&](Edge edge) {
if (_position > 0) {
const auto before = text[_position - 1];
if ((edge == Edge::Open && !isGood(before))
|| (edge == Edge::Close && isBad(before))) {
return false;
}
}
if (_position + tagLength < length) {
const auto after = text[_position + tagLength];
if ((edge == Edge::Open && isBad(after))
|| (edge == Edge::Close && !isGood(after))) {
return false;
}
}
return true;
};
const auto edgeIndex = static_cast<int>(edge);
if (_position >= 0) {
if (_checked[edgeIndex]) {
return;
} else if (check(edge)) {
_checked[edgeIndex] = true;
return;
} else {
_checked = { false, false };
}
}
while (true) {
_position = text.indexOf(tag, _offset);
if (_position < 0) {
_offset = _position = kInvalidPosition;
break;
}
_offset = _position + tagLength;
if (check(edge)) {
break;
} else {
continue;
}
}
if (_position == kInvalidPosition) {
_checked = { true, true };
} else {
_checked = { false, false };
_checked[edgeIndex] = true;
}
}
private:
int _offset = 0;
int _position = -1;
std::array<bool, 2> _checked = { false, false };
};
const std::vector<TagStartExpression> &TagStartExpressions() {
static auto cached = std::vector<TagStartExpression> {
{
@ -165,12 +247,12 @@ const std::vector<TagStartExpression> &TagStartExpressions() {
return cached;
}
const std::map<QString, std::vector<int>> &TagFinishIndices() {
static auto cached = std::map<QString, std::vector<int>> {
{ kTagBold, { kTagBoldIndex, kTagCodeIndex, kTagPreIndex } },
{ kTagItalic, { kTagItalicIndex, kTagCodeIndex, kTagPreIndex } },
{ kTagCode, { kTagCodeIndex, kTagPreIndex } },
{ kTagPre, { kTagPreIndex } },
const std::map<QString, int> &TagIndices() {
static auto cached = std::map<QString, int> {
{ kTagBold, kTagBoldIndex },
{ kTagItalic, kTagItalicIndex },
{ kTagCode, kTagCodeIndex },
{ kTagPre, kTagPreIndex },
};
return cached;
}
@ -179,12 +261,14 @@ bool DoesTagFinishByNewline(const QString &tag) {
return (tag == kTagCode);
}
class PossibleTagAccumulator {
class MarkdownTagAccumulator {
public:
PossibleTagAccumulator(std::vector<InputField::PossibleTag> *tags)
using Edge = TagSearchItem::Edge;
MarkdownTagAccumulator(std::vector<InputField::MarkdownTag> *tags)
: _tags(tags)
, _expressions(TagStartExpressions())
, _finishIndices(TagFinishIndices())
, _tagIndices(TagIndices())
, _items(_expressions.size()) {
}
@ -200,41 +284,49 @@ public:
return;
}
for (auto &item : _items) {
item = TagStartItem();
item = TagSearchItem();
}
auto tagIndex = _currentTag;
auto tryFinishTag = _currentTag;
while (true) {
for (; tagIndex != _currentFreeTag; ++tagIndex) {
auto &tag = (*_tags)[tagIndex];
bumpOffsetByTag(tag, tag.start + 1);
const auto finishIt = _finishIndices.find(tag.tag);
Assert(finishIt != end(_finishIndices));
const auto &finishingIndices = finishIt->second;
for (const auto index : finishingIndices) {
fillItem(index, text);
}
if (finishByNewline(tagIndex, text, finishingIndices)) {
for (; tryFinishTag != _currentFreeTag; ++tryFinishTag) {
auto &tag = (*_tags)[tryFinishTag];
if (tag.length >= 0) {
continue;
}
const auto min = minIndex(finishingIndices);
if (min >= 0) {
const auto minPosition = matchPosition(min);
finishTag(tagIndex, _currentLength + minPosition);
} else if (tag.tag == kTagPre || tag.tag == kTagCode) {
// We can't finish a mono tag, so we ignore all others.
return;
const auto i = _tagIndices.find(tag.tag);
Assert(i != end(_tagIndices));
const auto tagIndex = i->second;
_items[tagIndex].applyOffset(
tag.start + tag.tag.size() + 1 - _currentLength);
fillItem(
tagIndex,
text,
Edge::Close);
if (finishByNewline(tryFinishTag, text, tagIndex)) {
continue;
}
const auto position = matchPosition(tagIndex, Edge::Close);
if (position < kInvalidPosition) {
const auto till = position + tag.tag.size();
finishTag(
tryFinishTag,
_currentLength + till,
true);
_items[tagIndex].applyOffset(till);
}
}
for (auto i = 0, count = int(_items.size()); i != count; ++i) {
fillItem(i, text);
fillItem(i, text, Edge::Open);
}
const auto min = minIndex();
const auto min = minIndex(Edge::Open);
if (min < 0) {
return;
}
startTag(
_currentLength + matchPosition(min),
_currentLength + matchPosition(min, Edge::Open),
_expressions[min].tag);
}
}
@ -250,13 +342,14 @@ public:
}
private:
void finishTag(int index, int end) {
void finishTag(int index, int end, bool closed) {
Expects(_tags != nullptr);
Expects(index >= 0 && index < _tags->size());
auto &tag = (*_tags)[index];
if (tag.length < 0) {
tag.length = end - tag.start;
tag.closed = closed;
}
if (index == _currentTag) {
++_currentTag;
@ -265,7 +358,7 @@ private:
bool finishByNewline(
int index,
const QString &text,
const std::vector<int> &finishingIndices) {
int tagIndex) {
Expects(_tags != nullptr);
Expects(index >= 0 && index < _tags->size());
@ -277,93 +370,36 @@ private:
const auto endPosition = newlinePosition(
text,
std::max(0, tag.start + 1 - _currentLength));
for (const auto finishingIndex : finishingIndices) {
if (matchPosition(finishingIndex) <= endPosition) {
return false;
}
if (matchPosition(tagIndex, Edge::Close) <= endPosition) {
return false;
}
finishTag(index, _currentLength + endPosition);
finishTag(index, _currentLength + endPosition, false);
return true;
}
void bumpOffsetByTag(const InputField::PossibleTag &tag, int end) {
const auto offset = end - _currentLength;
if (tag.tag == kTagPre || tag.tag == kTagCode) {
for (auto &item : _items) {
applyOffset(item, offset);
}
} else if (tag.tag == kTagBold) {
applyOffset(_items[kTagBoldIndex], offset);
} else if (tag.tag == kTagItalic) {
applyOffset(_items[kTagItalicIndex], offset);
} else {
Unexpected("Unsupported tag.");
}
}
void applyOffset(TagStartItem &item, int offset) {
if (matchPosition(item) < offset) {
item.position = -1;
}
accumulate_max(item.offset, offset);
}
void finishTags() {
while (_currentTag != _currentFreeTag) {
finishTag(_currentTag, _currentLength);
finishTag(_currentTag, _currentLength, false);
}
}
void startTag(int offset, const QString &tag) {
Expects(_tags != nullptr);
if (_currentFreeTag < _tags->size()) {
(*_tags)[_currentFreeTag] = { offset, -1, tag };
(*_tags)[_currentFreeTag] = { offset, -1, false, tag };
} else {
_tags->push_back({ offset, -1, tag });
_tags->push_back({ offset, -1, false, tag });
}
++_currentFreeTag;
}
void fillItem(int index, const QString &text) {
void fillItem(int index, const QString &text, Edge edge) {
Expects(index >= 0 && index < _items.size());
auto &item = _items[index];
if (item.position >= 0) {
return;
}
const auto length = text.size();
const auto &expression = _expressions[index];
const auto &tag = expression.tag;
const auto &goodBefore = expression.goodBefore;
const auto &badAfter = expression.badAfter;
const auto tagLength = tag.size();
while (true) {
item.position = text.indexOf(tag, item.offset);
if (item.position < 0) {
item.offset = item.position = kInvalidPosition;
break;
}
item.offset = item.position + tagLength;
if (item.position > 0) {
const auto before = text[item.position - 1];
if (expression.goodBefore.indexOf(before) < 0) {
continue;
}
}
if (item.position + tagLength < length) {
const auto after = text[item.position + tagLength];
if (expression.badAfter.indexOf(after) >= 0) {
continue;
}
}
break;
}
item.offset = item.position + tagLength;
_items[index].fill(text, edge, _expressions[index]);
}
int matchPosition(int index) const {
int matchPosition(int index, Edge edge) const {
Expects(index >= 0 && index < _items.size());
return matchPosition(_items[index]);
}
int matchPosition(const TagStartItem &item) const {
const auto position = item.position;
return (item.position >= 0) ? item.position : kInvalidPosition;
return _items[index].matchPosition(edge);
}
int newlinePosition(const QString &text, int offset) const {
const auto length = text.size();
@ -377,11 +413,11 @@ private:
}
return kInvalidPosition;
}
int minIndex() const {
int minIndex(Edge edge) const {
auto result = -1;
auto minPosition = kInvalidPosition;
for (auto i = 0, count = int(_items.size()); i != count; ++i) {
const auto position = matchPosition(i);
const auto position = matchPosition(i, edge);
if (position < minPosition) {
minPosition = position;
result = i;
@ -389,11 +425,13 @@ private:
}
return result;
}
int minIndex(const std::vector<int> &indices) const {
int minIndexForFinish(const std::vector<int> &indices) const {
const auto tagIndex = indices[0];
auto result = -1;
auto minPosition = kInvalidPosition;
for (auto i : indices) {
const auto position = matchPosition(i);
const auto edge = (i == tagIndex) ? Edge::Close : Edge::Open;
const auto position = matchPosition(i, edge);
if (position < minPosition) {
minPosition = position;
result = i;
@ -402,10 +440,10 @@ private:
return result;
}
std::vector<InputField::PossibleTag> *_tags = nullptr;
std::vector<InputField::MarkdownTag> *_tags = nullptr;
const std::vector<TagStartExpression> &_expressions;
const std::map<QString, std::vector<int>> &_finishIndices;
std::vector<TagStartItem> _items;
const std::map<QString, int> &_tagIndices;
std::vector<TagSearchItem> _items;
int _currentTag = 0;
int _currentFreeTag = 0;
@ -1202,7 +1240,14 @@ void InputField::setMarkdownReplacesEnabled(rpl::producer<bool> enabled) {
std::move(
enabled
) | rpl::start_with_next([=](bool value) {
_markdownEnabled = value;
if (_markdownEnabled != value) {
_markdownEnabled = value;
if (_markdownEnabled) {
handleContentsChanged();
} else {
_lastMarkdownTags = {};
}
}
}, lifetime());
}
@ -1584,8 +1629,8 @@ QString InputField::getTextPart(
int end,
TagList &outTagsList,
bool &outTagsChanged,
std::vector<PossibleTag> *outPossibleTags) const {
Expects((start == 0 && end < 0) || outPossibleTags == nullptr);
std::vector<MarkdownTag> *outMarkdownTags) const {
Expects((start == 0 && end < 0) || outMarkdownTags == nullptr);
if (end >= 0 && end <= start) {
outTagsChanged = !outTagsList.isEmpty();
@ -1600,8 +1645,8 @@ QString InputField::getTextPart(
auto lastTag = QString();
TagAccumulator tagAccumulator(outTagsList);
PossibleTagAccumulator possibleTagAccumulator(outPossibleTags);
const auto newline = outPossibleTags ? QString(1, '\n') : QString();
MarkdownTagAccumulator markdownTagAccumulator(outMarkdownTags);
const auto newline = outMarkdownTags ? QString(1, '\n') : QString();
const auto document = _inner->document();
const auto from = full ? document->begin() : document->findBlock(start);
@ -1669,7 +1714,7 @@ QString InputField::getTextPart(
if (full || !text.isEmpty()) {
lastTag = format.property(kTagProperty).toString();
tagAccumulator.feed(lastTag, result.size());
possibleTagAccumulator.feed(text, lastTag);
markdownTagAccumulator.feed(text, lastTag);
}
auto begin = text.data();
@ -1700,13 +1745,13 @@ QString InputField::getTextPart(
block = block.next();
if (block != till) {
result.append('\n');
possibleTagAccumulator.feed(newline, lastTag);
markdownTagAccumulator.feed(newline, lastTag);
}
}
tagAccumulator.feed(QString(), result.size());
tagAccumulator.finish();
possibleTagAccumulator.finish();
markdownTagAccumulator.finish();
outTagsChanged = tagAccumulator.changed();
return result;
@ -2031,7 +2076,9 @@ void InputField::handleContentsChanged() {
-1,
_lastTextWithTags.tags,
tagsChanged,
_markdownEnabled ? &_textAreaPossibleTags : nullptr);
_markdownEnabled ? &_lastMarkdownTags : nullptr);
//highlightMarkdown();
if (tagsChanged || (_lastTextWithTags.text != currentText)) {
_lastTextWithTags.text = currentText;
@ -2042,6 +2089,36 @@ void InputField::handleContentsChanged() {
if (App::wnd()) App::wnd()->updateGlobalMenu();
}
void InputField::highlightMarkdown() {
// Highlighting may interfere with markdown parsing -> inaccurate.
// For debug.
auto from = 0;
auto applyColor = [&](int a, int b, QColor color) {
auto cursor = textCursor();
cursor.setPosition(a);
cursor.setPosition(b, QTextCursor::KeepAnchor);
auto format = QTextCharFormat();
format.setForeground(color);
cursor.mergeCharFormat(format);
from = b;
};
for (const auto &tag : _lastMarkdownTags) {
if (tag.start > from) {
applyColor(from, tag.start, QColor(0, 0, 0));
} else if (tag.start < from) {
continue;
}
applyColor(tag.start, tag.start + tag.length, tag.closed
? QColor(0, 128, 0)
: QColor(128, 0, 0));
}
auto cursor = textCursor();
cursor.movePosition(QTextCursor::End);
if (const auto till = cursor.position(); till > from) {
applyColor(from, till, QColor(0, 0, 0));
}
}
void InputField::onUndoAvailable(bool avail) {
_undoAvailable = avail;
if (App::wnd()) App::wnd()->updateGlobalMenu();
@ -2191,6 +2268,74 @@ TextWithTags InputField::getTextWithTagsPart(int start, int end) const {
return result;
}
TextWithTags InputField::getTextWithAppliedMarkdown() const {
if (!_markdownEnabled || _lastMarkdownTags.empty()) {
return getTextWithTags();
}
const auto &originalText = _lastTextWithTags.text;
const auto &originalTags = _lastTextWithTags.tags;
// Ignore tags that partially intersect some http-links.
// This will allow sending http://test.com/__test__/test correctly.
const auto links = TextUtilities::ParseEntities(
originalText,
0).entities;
auto result = TextWithTags();
result.text.reserve(originalText.size());
result.tags.reserve(originalTags.size() + _lastMarkdownTags.size());
auto from = 0;
auto removed = 0;
auto originalTag = originalTags.begin();
const auto originalTagsEnd = originalTags.end();
auto link = links.begin();
const auto linksEnd = links.end();
for (const auto &tag : _lastMarkdownTags) {
const auto tagLength = int(tag.tag.size());
if (!tag.closed || tag.start < from) {
continue;
}
const auto entityLength = tag.length - 2 * tagLength;
if (entityLength <= 0) {
continue;
}
while (originalTag != originalTagsEnd
&& originalTag->offset + originalTag->length <= tag.start) {
result.tags.push_back(*originalTag++);
result.tags.back().offset -= removed;
}
if (originalTag != originalTagsEnd
&& originalTag->offset < tag.start + tag.length) {
continue;
}
while (link != linksEnd
&& link->offset() + link->length() <= tag.start) {
++link;
}
if (link != linksEnd
&& link->offset() < tag.start + tag.length
&& (link->offset() + link->length() > tag.start + tag.length
|| link->offset() < tag.start)) {
continue;
}
if (tag.start > from) {
result.text.append(originalText.midRef(from, tag.start - from));
}
result.tags.push_back(TextWithTags::Tag{
int(result.text.size()),
entityLength,
tag.tag });
result.text.append(
originalText.midRef(tag.start + tagLength, entityLength));
from = tag.start + tag.length;
removed += 2 * tagLength;
}
if (originalText.size() > from) {
result.text.append(originalText.midRef(from));
}
return result;
}
void InputField::clear() {
_inner->clear();
startPlaceholderAnimation();
@ -2499,43 +2644,44 @@ const InstantReplaces &InputField::instantReplaces() const {
return _mutableInstantReplaces;
}
// Disable markdown instant replacement.
bool InputField::processMarkdownReplaces(const QString &appended) {
if (appended.size() != 1 || !_markdownEnabled) {
return false;
}
const auto ch = appended[0];
if (ch == '`') {
return processMarkdownReplace(kTagCode)
|| processMarkdownReplace(kTagPre);
} else if (ch == '*') {
return processMarkdownReplace(kTagBold);
} else if (ch == '_') {
return processMarkdownReplace(kTagItalic);
}
//if (appended.size() != 1 || !_markdownEnabled) {
// return false;
//}
//const auto ch = appended[0];
//if (ch == '`') {
// return processMarkdownReplace(kTagCode)
// || processMarkdownReplace(kTagPre);
//} else if (ch == '*') {
// return processMarkdownReplace(kTagBold);
//} else if (ch == '_') {
// return processMarkdownReplace(kTagItalic);
//}
return false;
}
bool InputField::processMarkdownReplace(const QString &tag) {
const auto position = textCursor().position();
const auto tagLength = tag.size();
const auto start = [&] {
for (const auto &possible : _textAreaPossibleTags) {
const auto end = possible.start + possible.length;
if (possible.start + 2 * tagLength >= position) {
return PossibleTag();
} else if (end >= position || end + tagLength == position) {
if (possible.tag == tag) {
return possible;
}
}
}
return PossibleTag();
}();
if (start.tag.isEmpty()) {
return false;
}
return commitMarkdownReplacement(start.start, position, tag, tag);
}
//bool InputField::processMarkdownReplace(const QString &tag) {
// const auto position = textCursor().position();
// const auto tagLength = tag.size();
// const auto start = [&] {
// for (const auto &possible : _lastMarkdownTags) {
// const auto end = possible.start + possible.length;
// if (possible.start + 2 * tagLength >= position) {
// return MarkdownTag();
// } else if (end >= position || end + tagLength == position) {
// if (possible.tag == tag) {
// return possible;
// }
// }
// }
// return MarkdownTag();
// }();
// if (start.tag.isEmpty()) {
// return false;
// }
// return commitMarkdownReplacement(start.start, position, tag, tag);
//}
void InputField::processInstantReplaces(const QString &appended) {
const auto &replaces = instantReplaces();
@ -2549,7 +2695,7 @@ void InputField::processInstantReplaces(const QString &appended) {
return;
}
const auto position = textCursor().position();
for (const auto &tag : _textAreaPossibleTags) {
for (const auto &tag : _lastMarkdownTags) {
if (tag.start < position
&& tag.start + tag.length >= position
&& (tag.tag == kTagCode || tag.tag == kTagPre)) {

View File

@ -124,9 +124,10 @@ public:
};
using TagList = TextWithTags::Tags;
struct PossibleTag {
struct MarkdownTag {
int start = 0;
int length = 0;
bool closed = false;
QString tag;
};
static const QString kTagBold;
@ -161,7 +162,11 @@ public:
const TextWithTags &getTextWithTags() const {
return _lastTextWithTags;
}
const std::vector<MarkdownTag> &getMarkdownTags() const {
return _lastMarkdownTags;
}
TextWithTags getTextWithTagsPart(int start, int end = -1) const;
TextWithTags getTextWithAppliedMarkdown() const;
void insertTag(const QString &text, QString tagId = QString());
bool empty() const {
return _lastTextWithTags.text.isEmpty();
@ -352,7 +357,7 @@ private:
int end,
TagList &outTagsList,
bool &outTagsChanged,
std::vector<PossibleTag> *outPossibleTags = nullptr) const;
std::vector<MarkdownTag> *outMarkdownTags = nullptr) const;
// After any characters added we must postprocess them. This includes:
// 1. Replacing font family to semibold for ~ characters, if we used Open Sans 13px.
@ -366,7 +371,7 @@ private:
void chopByMaxLength(int insertPosition, int insertLength);
bool processMarkdownReplaces(const QString &appended);
bool processMarkdownReplace(const QString &tag);
//bool processMarkdownReplace(const QString &tag);
void addMarkdownActions(not_null<QMenu*> menu, QContextMenuEvent *e);
void addMarkdownMenuAction(
not_null<QMenu*> menu,
@ -390,6 +395,8 @@ private:
bool revertFormatReplace();
void highlightMarkdown();
const style::InputField &_st;
Mode _mode = Mode::SingleLine;
@ -402,7 +409,7 @@ private:
object_ptr<Inner> _inner;
TextWithTags _lastTextWithTags;
std::vector<PossibleTag> _textAreaPossibleTags;
std::vector<MarkdownTag> _lastMarkdownTags;
QString _lastPreEditText;
base::lambda<bool(
EditLinkSelection selection,

View File

@ -796,7 +796,7 @@ void Notification::sendReply() {
manager()->notificationReplied(
peerId,
msgId,
_replyArea->getTextWithTags());
_replyArea->getTextWithAppliedMarkdown());
manager()->startAllHiding();
}