From 3a67e4f1f45bb210a98a5e752c5777c26165d2f8 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 10 Nov 2023 16:12:38 +0400 Subject: [PATCH] Support highlighting correct quoted part. --- Telegram/SourceFiles/history/history_item.cpp | 1 + .../SourceFiles/history/history_widget.cpp | 18 ++++- Telegram/SourceFiles/history/history_widget.h | 3 +- .../controls/history_view_draft_options.cpp | 6 +- .../history/view/history_view_element.cpp | 77 +++++++++++++++---- .../view/history_view_replies_section.cpp | 7 +- .../history/view/history_view_reply.cpp | 6 +- .../view/history_view_scheduled_section.cpp | 7 +- 8 files changed, 101 insertions(+), 24 deletions(-) diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index ec13d776c..8dfe07e5d 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -2929,6 +2929,7 @@ FullReplyTo HistoryItem::replyTo() const { if (const auto id = fields.messageId) { result.messageId = { replyToPeer, id }; result.quote = fields.quote; + result.quoteOffset = fields.quoteOffset; } if (const auto id = fields.storyId) { result.storyId = { replyToPeer, id }; diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index 849234013..e2df00a16 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -6331,7 +6331,12 @@ void HistoryWidget::editDraftOptions() { void HistoryWidget::jumpToReply(FullReplyTo to) { if (const auto item = session().data().message(to.messageId)) { - JumpToMessageClickHandler(item, {}, to.quote)->onClick({}); + JumpToMessageClickHandler( + item, + {}, + to.quote, + to.quoteOffset + )->onClick({}); } } @@ -7158,17 +7163,22 @@ void HistoryWidget::clearFieldText( void HistoryWidget::replyToMessage(FullReplyTo id) { if (const auto item = session().data().message(id.messageId)) { - replyToMessage(item, id.quote); + replyToMessage(item, id.quote, id.quoteOffset); } } void HistoryWidget::replyToMessage( not_null item, - TextWithEntities quote) { + TextWithEntities quote, + int quoteOffset) { if (isJoinChannel()) { return; } - _processingReplyTo = { .messageId = item->fullId(), .quote = quote }; + _processingReplyTo = { + .messageId = item->fullId(), + .quote = quote, + .quoteOffset = quoteOffset, + }; _processingReplyItem = item; processReply(); } diff --git a/Telegram/SourceFiles/history/history_widget.h b/Telegram/SourceFiles/history/history_widget.h index 51d5aac84..a676ac80f 100644 --- a/Telegram/SourceFiles/history/history_widget.h +++ b/Telegram/SourceFiles/history/history_widget.h @@ -195,7 +195,8 @@ public: void replyToMessage(FullReplyTo id); void replyToMessage( not_null item, - TextWithEntities quote = {}); + TextWithEntities quote = {}, + int quoteOffset = 0); void editMessage(FullMsgId itemId); void editMessage(not_null item); diff --git a/Telegram/SourceFiles/history/view/controls/history_view_draft_options.cpp b/Telegram/SourceFiles/history/view/controls/history_view_draft_options.cpp index 60abbf021..9c7ce70c5 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_draft_options.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_draft_options.cpp @@ -601,7 +601,11 @@ void DraftOptionsBox( rpl::lifetime resolveLifetime; }; const auto state = box->lifetime().make_state(); - state->quote = SelectedQuote{ replyItem, draft.reply.quote }; + state->quote = SelectedQuote{ + replyItem, + draft.reply.quote, + draft.reply.quoteOffset, + }; state->webpage = draft.webpage; state->preview = previewData; state->shown = previewData ? Section::Link : Section::Reply; diff --git a/Telegram/SourceFiles/history/view/history_view_element.cpp b/Telegram/SourceFiles/history/view/history_view_element.cpp index be4e9f941..a10c6e85d 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.cpp +++ b/Telegram/SourceFiles/history/view/history_view_element.cpp @@ -1659,22 +1659,71 @@ TextSelection Element::FindSelectionFromQuote( return {}; } const auto &original = quote.item->originalText(); - auto result = TextSelection(); - auto offset = 0; - while (true) { - const auto i = original.text.indexOf(quote.text.text, offset); - if (i < 0) { - return {}; - } - auto selection = TextSelection{ - uint16(i), - uint16(i + quote.text.text.size()), + const auto length = int(original.text.size()); + const auto qlength = int(quote.text.text.size()); + const auto checkAt = [&](int offset) { + const auto selection = TextSelection{ + uint16(offset), + uint16(offset + qlength), }; - if (CheckQuoteEntities(quote.text.entities, original, selection)) { - result = selection; - break; + return CheckQuoteEntities(quote.text.entities, original, selection) + ? selection + : TextSelection{ uint16(offset + 1), uint16(offset + 1) }; + }; + const auto findOneAfter = [&](int offset) { + if (offset > length - qlength) { + return TextSelection(); } - offset = i + 1; + const auto i = original.text.indexOf(quote.text.text, offset); + return (i >= 0) ? checkAt(i) : TextSelection(); + }; + const auto findOneBefore = [&](int offset) { + if (!offset) { + return TextSelection(); + } + const auto end = std::min(offset + qlength - 1, length); + const auto from = end - length - 1; + const auto i = original.text.lastIndexOf(quote.text.text, from); + return (i >= 0) ? checkAt(i) : TextSelection(); + }; + const auto findAfter = [&](int offset) { + while (true) { + const auto result = findOneAfter(offset); + if (!result.empty() || result == TextSelection()) { + return result; + } + offset = result.from; + } + }; + const auto findBefore = [&](int offset) { + while (true) { + const auto result = findOneBefore(offset); + if (!result.empty() || result == TextSelection()) { + return result; + } + offset = result.from - 2; + if (offset < 0) { + return result; + } + } + }; + const auto findTwoWays = [&](int offset) { + const auto after = findAfter(offset); + if (after.empty()) { + return findBefore(offset); + } else if (after.from == offset) { + return after; + } + const auto before = findBefore(offset); + return before.empty() + ? after + : (offset - before.from < after.from - offset) + ? before + : after; + }; + auto result = findTwoWays(quote.offset); + if (result.empty()) { + return {}; } for (const auto &modification : text.modifications()) { if (modification.position >= result.to) { diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp index 24c1ba4a4..60a49af12 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp @@ -797,7 +797,12 @@ void RepliesWidget::setupComposeControls() { _composeControls->jumpToItemRequests( ) | rpl::start_with_next([=](FullReplyTo to) { if (const auto item = session().data().message(to.messageId)) { - JumpToMessageClickHandler(item, {}, to.quote)->onClick({}); + JumpToMessageClickHandler( + item, + {}, + to.quote, + to.quoteOffset + )->onClick({}); } }, lifetime()); diff --git a/Telegram/SourceFiles/history/view/history_view_reply.cpp b/Telegram/SourceFiles/history/view/history_view_reply.cpp index 10877d5c1..3da6c9952 100644 --- a/Telegram/SourceFiles/history/view/history_view_reply.cpp +++ b/Telegram/SourceFiles/history/view/history_view_reply.cpp @@ -270,6 +270,7 @@ void Reply::setLinkFrom( const auto quote = fields.manualQuote ? fields.quote : TextWithEntities(); + const auto quoteOffset = fields.quoteOffset; const auto returnToId = view->data()->fullId(); const auto externalLink = [=](ClickContext context) { const auto my = context.other.value(); @@ -292,7 +293,8 @@ void Reply::setLinkFrom( channel, messageId, returnToId, - quote + quote, + quoteOffset )->onClick(context); } else { controller->showPeerInfo(channel); @@ -313,7 +315,7 @@ void Reply::setLinkFrom( const auto message = data->resolvedMessage.get(); const auto story = data->resolvedStory.get(); _link = message - ? JumpToMessageClickHandler(message, returnToId, quote) + ? JumpToMessageClickHandler(message, returnToId, quote, quoteOffset) : story ? JumpToStoryClickHandler(story) : (data->external() diff --git a/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp b/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp index e89a755d4..ed57d5ee8 100644 --- a/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp @@ -281,7 +281,12 @@ void ScheduledWidget::setupComposeControls() { if (item->isScheduled() && item->history() == _history) { showAtPosition(item->position()); } else { - JumpToMessageClickHandler(item, {}, to.quote)->onClick({}); + JumpToMessageClickHandler( + item, + {}, + to.quote, + to.quoteOffset + )->onClick({}); } } }, lifetime());