diff --git a/Telegram/SourceFiles/data/data_msg_id.h b/Telegram/SourceFiles/data/data_msg_id.h index 93162e182..575627d71 100644 --- a/Telegram/SourceFiles/data/data_msg_id.h +++ b/Telegram/SourceFiles/data/data_msg_id.h @@ -88,6 +88,7 @@ constexpr auto SwitchAtTopMsgId = MsgId(SpecialMsgIdShift + 2); constexpr auto ShowAndStartBotMsgId = MsgId(SpecialMsgIdShift + 4); constexpr auto ShowAndMaybeStartBotMsgId = MsgId(SpecialMsgIdShift + 5); constexpr auto ShowForChooseMessagesMsgId = MsgId(SpecialMsgIdShift + 6); +constexpr auto kSearchQueryOffsetHint = -1; static_assert(SpecialMsgIdShift + 0xFF < 0); static_assert(-(SpecialMsgIdShift + 0xFF) > ServerMaxMsgId); diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp index 14cabc9d5..a7206c647 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp @@ -696,16 +696,19 @@ void Widget::chosenRow(const ChosenRow &row) { } return; } else if (const auto topic = row.key.topic()) { + auto params = Window::SectionShow( + Window::SectionShow::Way::ClearStack); + params.highlightPart.text = _searchState.query; + if (!params.highlightPart.empty()) { + params.highlightPartOffsetHint = kSearchQueryOffsetHint; + } if (row.newWindow) { controller()->showInNewWindow( Window::SeparateId(topic), row.message.fullId.msg); } else { session().data().saveViewAsMessages(topic->forum(), false); - controller()->showThread( - topic, - row.message.fullId.msg, - Window::SectionShow::Way::ClearStack); + controller()->showThread(topic, row.message.fullId.msg, params); } } else if (history && row.userpicClick @@ -742,13 +745,16 @@ void Widget::chosenRow(const ChosenRow &row) { const auto showAtMsgId = controller()->uniqueChatsInSearchResults() ? ShowAtUnreadMsgId : row.message.fullId.msg; + auto params = Window::SectionShow( + Window::SectionShow::Way::ClearStack); + params.highlightPart.text = _searchState.query; + if (!params.highlightPart.empty()) { + params.highlightPartOffsetHint = kSearchQueryOffsetHint; + } if (row.newWindow) { controller()->showInNewWindow(peer, showAtMsgId); } else { - controller()->showThread( - history, - showAtMsgId, - Window::SectionShow::Way::ClearStack); + controller()->showThread(history, showAtMsgId, params); hideChildList(); } } else if (const auto folder = row.key.folder()) { diff --git a/Telegram/SourceFiles/history/view/history_view_element.cpp b/Telegram/SourceFiles/history/view/history_view_element.cpp index 3b6dde0c8..2e71bb7b8 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.cpp +++ b/Telegram/SourceFiles/history/view/history_view_element.cpp @@ -94,6 +94,89 @@ Element *MousedElement/* = nullptr*/; return session->tryResolveWindow(); } +[[nodiscard]] TextSelection FindSearchQueryHighlight( + const QString &text, + const QString &query) { + const auto lower = query.toLower(); + const auto inside = text.toLower(); + const auto find = [&](QStringView part) { + auto skip = 0; + if (const auto from = inside.indexOf(part, skip); from >= 0) { + if (!from || !inside[from - 1].isLetterOrNumber()) { + return from; + } + skip = from + 1; + } + return -1; + }; + if (const auto from = find(lower); from >= 0) { + const auto till = from + query.size(); + if (till >= inside.size() || !inside[till].isLetterOrNumber()) { + return { uint16(from), uint16(till) }; + } + } + const auto tillEndOfWord = [&](int from) { + for (auto till = from + 1; till != inside.size(); ++till) { + if (!inside[till].isLetterOrNumber()) { + return TextSelection{ uint16(from), uint16(till) }; + } + } + return TextSelection{ uint16(from), uint16(inside.size()) }; + }; + const auto words = QStringView(lower).split( + QRegularExpression( + u"[\\W]"_q, + QRegularExpression::UseUnicodePropertiesOption), + Qt::SkipEmptyParts); + for (const auto &word : words) { + const auto length = int(word.size()); + const auto cut = length / 2; + const auto part = word.mid(0, length - cut); + const auto offset = find(part); + if (offset < 0) { + continue; + } + for (auto i = 0; i != cut; ++i) { + const auto part = word.mid(0, length - i); + if (const auto from = find(part); from >= 0) { + return tillEndOfWord(from); + } + } + return tillEndOfWord(offset); + } + return {}; +} + +[[nodiscard]] TextSelection ApplyModificationsFrom( + TextSelection result, + const Ui::Text::String &text) { + if (result.empty()) { + return result; + } + for (const auto &modification : text.modifications()) { + if (modification.position >= result.to) { + break; + } + if (modification.added) { + ++result.to; + } + const auto shiftTo = std::min( + int(modification.skipped), + result.to - modification.position); + result.to -= shiftTo; + if (modification.position <= result.from) { + if (modification.added) { + ++result.from; + } + const auto shiftFrom = std::min( + int(modification.skipped), + result.from - modification.position); + result.from -= shiftFrom; + } + } + return result; +} + } // namespace std::unique_ptr MakePathShiftGradient( @@ -1726,6 +1809,11 @@ TextSelection Element::FindSelectionFromQuote( return {}; } const auto &original = quote.item->originalText(); + if (quote.offset == kSearchQueryOffsetHint) { + return ApplyModificationsFrom( + FindSearchQueryHighlight(original.text, quote.text.text), + text); + } const auto length = int(original.text.size()); const auto qlength = int(quote.text.text.size()); const auto checkAt = [&](int offset) { @@ -1789,28 +1877,7 @@ TextSelection Element::FindSelectionFromQuote( if (result.empty()) { return {}; } - for (const auto &modification : text.modifications()) { - if (modification.position >= result.to) { - break; - } - if (modification.added) { - ++result.to; - } - const auto shiftTo = std::min( - int(modification.skipped), - result.to - modification.position); - result.to -= shiftTo; - if (modification.position <= result.from) { - if (modification.added) { - ++result.from; - } - const auto shiftFrom = std::min( - int(modification.skipped), - result.from - modification.position); - result.from -= shiftFrom; - } - } - return result; + return ApplyModificationsFrom(result, text); } Reactions::ButtonParameters Element::reactionButtonParameters(