From 2414e927bd2e4fa313932d14fa285bb91550b666 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 3 Oct 2023 18:22:59 +0400 Subject: [PATCH] Add initial code syntax highlighting. Thanks PrismJS and Fela for porting it to C++. --- Telegram/CMakeLists.txt | 1 + Telegram/SourceFiles/data/data_session.cpp | 34 ++++++++++++++++++ Telegram/SourceFiles/data/data_session.h | 6 ++++ Telegram/SourceFiles/data/data_types.h | 2 ++ Telegram/SourceFiles/history/history_item.cpp | 22 ++++++++++-- Telegram/SourceFiles/history/history_item.h | 4 ++- .../history/view/history_view_message.cpp | 1 + .../view/media/history_view_document.cpp | 1 + .../media/history_view_extended_preview.cpp | 1 + .../history/view/media/history_view_gif.cpp | 1 + .../history/view/media/history_view_photo.cpp | 1 + Telegram/SourceFiles/ui/chat/chat_style.cpp | 35 +++++++++++++++++++ Telegram/SourceFiles/ui/chat/chat_style.h | 4 +++ Telegram/lib_spellcheck | 2 +- Telegram/lib_ui | 2 +- 15 files changed, 112 insertions(+), 5 deletions(-) diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 01d67891c..130aca8bc 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -1540,6 +1540,7 @@ elseif (APPLE) PRE_LINK COMMAND mkdir -p $/../Resources COMMAND cp ${CMAKE_BINARY_DIR}/lib_ui.rcc $/../Resources + COMMAND cp ${CMAKE_BINARY_DIR}/lib_spellcheck.rcc $/../Resources ) if (NOT build_macstore) add_custom_command(TARGET Telegram diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp index 68ef1afa2..4eba210f4 100644 --- a/Telegram/SourceFiles/data/data_session.cpp +++ b/Telegram/SourceFiles/data/data_session.cpp @@ -80,6 +80,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/unixtime.h" #include "base/call_delayed.h" #include "base/random.h" +#include "spellcheck/spellcheck_highlight_syntax.h" #include "styles/style_boxes.h" // st::backgroundSize namespace Data { @@ -300,6 +301,11 @@ Session::Session(not_null session) } }, _lifetime); + Spellchecker::HighlightReady( + ) | rpl::start_with_next([=](uint64 processId) { + highlightProcessDone(processId); + }, _lifetime); + subscribeForTopicRepliesLists(); crl::on_main(_session, [=] { @@ -1760,6 +1766,27 @@ void Session::requestItemTextRefresh(not_null item) { } } +void Session::registerHighlightProcess( + uint64 processId, + not_null item) { + Expects(item->inHighlightProcess()); + + const auto [i, ok] = _highlightings.emplace(processId, item); + + Ensures(ok); +} + +void Session::highlightProcessDone(uint64 processId) { + if (const auto done = _highlightings.take(processId)) { + for (const auto &[id, item] : _highlightings) { + if (item == *done) { + return; + } + } + (*done)->highlightProcessDone(); + } +} + void Session::requestUnreadReactionsAnimation(not_null item) { enumerateItemViews(item, [&](not_null view) { view->animateUnreadReactions(); @@ -2415,6 +2442,13 @@ void Session::unregisterMessage(not_null item) { Data::MessageUpdate::Flag::Destroyed); groups().unregisterMessage(item); removeDependencyMessage(item); + for (auto i = begin(_highlightings); i != end(_highlightings);) { + if (i->second == item) { + i = _highlightings.erase(i); + } else { + ++i; + } + } messagesListForInsert(peerId)->erase(itemId); if (!peerIsChannel(peerId) && IsServerMsgId(itemId)) { diff --git a/Telegram/SourceFiles/data/data_session.h b/Telegram/SourceFiles/data/data_session.h index 9ba13d1a9..86cb08c6f 100644 --- a/Telegram/SourceFiles/data/data_session.h +++ b/Telegram/SourceFiles/data/data_session.h @@ -299,6 +299,10 @@ public: void notifyPinnedDialogsOrderUpdated(); [[nodiscard]] rpl::producer<> pinnedDialogsOrderUpdated() const; + void registerHighlightProcess( + uint64 processId, + not_null item); + void registerHeavyViewPart(not_null view); void unregisterHeavyViewPart(not_null view); void unloadHeavyViewParts( @@ -845,6 +849,7 @@ private: TimeId date); void setWallpapers(const QVector &data, uint64 hash); + void highlightProcessDone(uint64 processId); void checkPollsClosings(); @@ -955,6 +960,7 @@ private: std::unordered_map< FullStoryId, base::flat_set>> _storyItems; + base::flat_map> _highlightings; base::flat_set> _webpagesUpdated; base::flat_set> _gamesUpdated; diff --git a/Telegram/SourceFiles/data/data_types.h b/Telegram/SourceFiles/data/data_types.h index a296e6cf4..1c2823a4f 100644 --- a/Telegram/SourceFiles/data/data_types.h +++ b/Telegram/SourceFiles/data/data_types.h @@ -303,6 +303,8 @@ enum class MessageFlag : uint64 { FakeBotAbout = (1ULL << 36), StoryItem = (1ULL << 37), + + InHighlightProcess = (1ULL << 38), }; inline constexpr bool is_flag_type(MessageFlag) { return true; } using MessageFlags = base::flags; diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index 746be6038..f5d04718d 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -67,6 +67,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_web_page.h" #include "chat_helpers/stickers_gift_box_pack.h" #include "payments/payments_checkout_process.h" // CheckoutProcess::Start. +#include "spellcheck/spellcheck_highlight_syntax.h" #include "styles/style_dialogs.h" namespace { @@ -2871,15 +2872,32 @@ void HistoryItem::setText(const TextWithEntities &textWithEntities) { : std::move(textWithEntities)); } -void HistoryItem::setTextValue(TextWithEntities text) { +void HistoryItem::setTextValue(TextWithEntities text, bool force) { + if (const auto processId = Spellchecker::TryHighlightSyntax(text)) { + _flags |= MessageFlag::InHighlightProcess; + history()->owner().registerHighlightProcess(processId, this); + } const auto had = !_text.empty(); _text = std::move(text); RemoveComponents(HistoryMessageTranslation::Bit()); - if (had) { + if (had || force) { history()->owner().requestItemTextRefresh(this); } } +bool HistoryItem::inHighlightProcess() const { + return _flags & MessageFlag::InHighlightProcess; +} + +void HistoryItem::highlightProcessDone() { + Expects(inHighlightProcess()); + + _flags &= ~MessageFlag::InHighlightProcess; + if (!_text.empty()) { + setTextValue(base::take(_text), true); + } +} + bool HistoryItem::showNotification() const { const auto channel = _history->peer->asChannel(); if (channel && !channel->amIn()) { diff --git a/Telegram/SourceFiles/history/history_item.h b/Telegram/SourceFiles/history/history_item.h index 7e54081dc..d47660393 100644 --- a/Telegram/SourceFiles/history/history_item.h +++ b/Telegram/SourceFiles/history/history_item.h @@ -322,6 +322,8 @@ public: [[nodiscard]] bool repliesAreComments() const; [[nodiscard]] bool externalReply() const; [[nodiscard]] bool hasExtendedMediaPreview() const; + [[nodiscard]] bool inHighlightProcess() const; + void highlightProcessDone(); void setCommentsInboxReadTill(MsgId readTillId); void setCommentsMaxId(MsgId maxId); @@ -537,7 +539,7 @@ private: [[nodiscard]] bool generateLocalEntitiesByReply() const; [[nodiscard]] TextWithEntities withLocalEntities( const TextWithEntities &textWithEntities) const; - void setTextValue(TextWithEntities text); + void setTextValue(TextWithEntities text, bool force = false); [[nodiscard]] bool isTooOldForEdit(TimeId now) const; [[nodiscard]] bool isLegacyMessage() const { return _flags & MessageFlag::Legacy; diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp index aa453c225..13d9efb5d 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_message.cpp @@ -1622,6 +1622,7 @@ void Message::paintText( .position = trect.topLeft(), .availableWidth = trect.width(), .palette = &stm->textPalette, + .colors = context.st->highlightColors(), .spoiler = Ui::Text::DefaultSpoilerCache(), .now = context.now, .pausedEmoji = context.paused || On(PowerSaving::kEmojiChat), diff --git a/Telegram/SourceFiles/history/view/media/history_view_document.cpp b/Telegram/SourceFiles/history/view/media/history_view_document.cpp index d1ddb2b96..5caedc79f 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_document.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_document.cpp @@ -748,6 +748,7 @@ void Document::draw( .position = { st::msgPadding.left(), captiontop }, .availableWidth = captionw, .palette = &stm->textPalette, + .colors = context.st->highlightColors(), .spoiler = Ui::Text::DefaultSpoilerCache(), .now = context.now, .pausedEmoji = context.paused || On(PowerSaving::kEmojiChat), diff --git a/Telegram/SourceFiles/history/view/media/history_view_extended_preview.cpp b/Telegram/SourceFiles/history/view/media/history_view_extended_preview.cpp index f2768fef1..252b16a4d 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_extended_preview.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_extended_preview.cpp @@ -235,6 +235,7 @@ void ExtendedPreview::draw(Painter &p, const PaintContext &context) const { painty + painth + st::mediaCaptionSkip), .availableWidth = captionw, .palette = &stm->textPalette, + .colors = context.st->highlightColors(), .spoiler = Ui::Text::DefaultSpoilerCache(), .now = context.now, .pausedEmoji = context.paused || On(PowerSaving::kEmojiChat), diff --git a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp index ba5a5bbef..d2ec6a551 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp @@ -709,6 +709,7 @@ void Gif::draw(Painter &p, const PaintContext &context) const { .position = QPoint(st::msgPadding.left(), top), .availableWidth = captionw, .palette = &stm->textPalette, + .colors = context.st->highlightColors(), .spoiler = Ui::Text::DefaultSpoilerCache(), .now = context.now, .pausedEmoji = context.paused || On(PowerSaving::kEmojiChat), diff --git a/Telegram/SourceFiles/history/view/media/history_view_photo.cpp b/Telegram/SourceFiles/history/view/media/history_view_photo.cpp index 0c8849699..034e52d6c 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_photo.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_photo.cpp @@ -405,6 +405,7 @@ void Photo::draw(Painter &p, const PaintContext &context) const { .position = QPoint(st::msgPadding.left(), top), .availableWidth = captionw, .palette = &stm->textPalette, + .colors = context.st->highlightColors(), .spoiler = Ui::Text::DefaultSpoilerCache(), .now = context.now, .pausedEmoji = context.paused || On(PowerSaving::kEmojiChat), diff --git a/Telegram/SourceFiles/ui/chat/chat_style.cpp b/Telegram/SourceFiles/ui/chat/chat_style.cpp index bb93cf9e8..d188992ce 100644 --- a/Telegram/SourceFiles/ui/chat/chat_style.cpp +++ b/Telegram/SourceFiles/ui/chat/chat_style.cpp @@ -446,6 +446,41 @@ void ChatStyle::applyAdjustedServiceBg(QColor serviceBg) { msgServiceBg().set(uchar(r), uchar(g), uchar(b), uchar(a)); } +std::span ChatStyle::highlightColors() const { + if (_highlightColors.empty()) { + const auto push = [&](const style::color &color) { + _highlightColors.push_back({ &color->p, &color->p }); + }; + + // comment, block-comment, prolog, doctype, cdata + push(statisticsChartLineLightblue()); + + // punctuation + push(statisticsChartLineRed()); + + // property, tag, boolean, number, + // constant, symbol, deleted + push(statisticsChartLineRed()); + + // selector, attr-name, string, char, builtin, inserted + push(statisticsChartLineOrange()); + + // operator, entity, url + push(statisticsChartLineRed()); + + // atrule, attr-value, keyword, function + push(statisticsChartLineBlue()); + + // class-name + push(statisticsChartLinePurple()); + + //push(statisticsChartLineLightgreen()); + //push(statisticsChartLineGreen()); + //push(statisticsChartLineGolden()); + } + return _highlightColors; +} + void ChatStyle::assignPalette(not_null palette) { *static_cast(this) = *palette; style::internal::resetIcons(); diff --git a/Telegram/SourceFiles/ui/chat/chat_style.h b/Telegram/SourceFiles/ui/chat/chat_style.h index f1fd0b807..cefe34a19 100644 --- a/Telegram/SourceFiles/ui/chat/chat_style.h +++ b/Telegram/SourceFiles/ui/chat/chat_style.h @@ -168,6 +168,8 @@ public: void applyCustomPalette(const style::palette *palette); void applyAdjustedServiceBg(QColor serviceBg); + [[nodiscard]] std::span highlightColors() const; + [[nodiscard]] rpl::producer<> paletteChanged() const { return _paletteChanged.events(); } @@ -332,6 +334,8 @@ private: mutable CornersPixmaps _msgSelectOverlayCorners[ int(CachedCornerRadius::kCount)]; + mutable std::vector _highlightColors; + style::TextPalette _historyPsaForwardPalette; style::TextPalette _imgReplyTextPalette; style::TextPalette _serviceTextPalette; diff --git a/Telegram/lib_spellcheck b/Telegram/lib_spellcheck index ab2a9d1c1..a9fb03101 160000 --- a/Telegram/lib_spellcheck +++ b/Telegram/lib_spellcheck @@ -1 +1 @@ -Subproject commit ab2a9d1c19b948a05b216801f8ba4fea0759428a +Subproject commit a9fb0310132bc645ffcfb3734bd2ca53f1fb39fa diff --git a/Telegram/lib_ui b/Telegram/lib_ui index 02440524e..0d8717d48 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit 02440524eaa2b1bd6d1909ff4aa2ca207a282b2c +Subproject commit 0d8717d48ac3f2228399a1de78a28f0a7bbd0023