From fcef1c2e2ca5050f5198ce03a439b791cd4daf1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Lucas=20Golini?= Date: Fri, 3 Jul 2026 12:45:33 -0300 Subject: [PATCH 1/2] Syntax highlighting support for Gemini documents (SpartanJ/ecode#917). --- .../src/eepp/ui/doc/languages/gemini.cpp | 91 +++++++++++++++++++ .../src/eepp/ui/doc/languages/gemini.hpp | 11 +++ .../ui/doc/languagessyntaxhighlighting.cpp | 4 + 3 files changed, 106 insertions(+) create mode 100644 src/modules/languages-syntax-highlighting/src/eepp/ui/doc/languages/gemini.cpp create mode 100644 src/modules/languages-syntax-highlighting/src/eepp/ui/doc/languages/gemini.hpp diff --git a/src/modules/languages-syntax-highlighting/src/eepp/ui/doc/languages/gemini.cpp b/src/modules/languages-syntax-highlighting/src/eepp/ui/doc/languages/gemini.cpp new file mode 100644 index 000000000..19b6baaf6 --- /dev/null +++ b/src/modules/languages-syntax-highlighting/src/eepp/ui/doc/languages/gemini.cpp @@ -0,0 +1,91 @@ +#include +#include + +namespace EE { namespace UI { namespace Doc { namespace Language { + +SyntaxDefinition& addGemini() { + + return SyntaxDefinitionManager::instance() + ->add( + + { "Gemini", + { "%.gmi$" }, + { + { { "include", "#headings" }, "normal", "", SyntaxPatternMatchType::LuaPattern }, + { { "include", "#links" }, "normal", "", SyntaxPatternMatchType::LuaPattern }, + { { "include", "#quote" }, "normal", "", SyntaxPatternMatchType::LuaPattern }, + { { "include", "#raw" }, "normal", "", SyntaxPatternMatchType::LuaPattern }, + { { "include", "#unorderedLists" }, + "normal", + "", + SyntaxPatternMatchType::LuaPattern }, + + }, + { + + }, + "", + {} + + } ) + .addRepositories( { + + { "unorderedLists", + { + { { "^(\\*)[ \t]+.+\n" }, + { "normal", "operator" }, + {}, + "", + SyntaxPatternMatchType::RegEx }, + + } }, + { "raw", + { + { { "^```.*\n", "^```.*\n" }, + { "normal" }, + {}, + "", + SyntaxPatternMatchType::RegEx }, + + } }, + { "quote", + { + { { "^(>).*$" }, + { "string", "operator" }, + {}, + "", + SyntaxPatternMatchType::RegEx }, + + } }, + { "links", + { + { { "^=>[ \t]+([^ \t]+)(?:[ \t]+(.*))?" }, + { "normal", "link", "string" }, + {}, + "", + SyntaxPatternMatchType::RegEx }, + + } }, + { "headings", + { + { { "^(#)(?:[^#].*)?\n" }, + { "keyword", "operator" }, + {}, + "", + SyntaxPatternMatchType::RegEx }, + { { "^(##)(?:[^#].*)?\n" }, + { "keyword", "operator" }, + {}, + "", + SyntaxPatternMatchType::RegEx }, + { { "^(###)(?:[^#].*)?\n" }, + { "keyword", "operator" }, + {}, + "", + SyntaxPatternMatchType::RegEx }, + + } }, + } ); +} + +}}}} // namespace EE::UI::Doc::Language diff --git a/src/modules/languages-syntax-highlighting/src/eepp/ui/doc/languages/gemini.hpp b/src/modules/languages-syntax-highlighting/src/eepp/ui/doc/languages/gemini.hpp new file mode 100644 index 000000000..ded1aeeea --- /dev/null +++ b/src/modules/languages-syntax-highlighting/src/eepp/ui/doc/languages/gemini.hpp @@ -0,0 +1,11 @@ +#ifndef EE_UI_DOC_Gemini +#define EE_UI_DOC_Gemini +#include + +namespace EE { namespace UI { namespace Doc { namespace Language { + +extern SyntaxDefinition& addGemini(); + +}}}} // namespace EE::UI::Doc::Language + +#endif diff --git a/src/modules/languages-syntax-highlighting/src/eepp/ui/doc/languagessyntaxhighlighting.cpp b/src/modules/languages-syntax-highlighting/src/eepp/ui/doc/languagessyntaxhighlighting.cpp index 293f2f6a6..61cfb8b12 100644 --- a/src/modules/languages-syntax-highlighting/src/eepp/ui/doc/languagessyntaxhighlighting.cpp +++ b/src/modules/languages-syntax-highlighting/src/eepp/ui/doc/languagessyntaxhighlighting.cpp @@ -38,6 +38,7 @@ #include #include #include +#include #include #include #include @@ -359,6 +360,9 @@ static void preDefinitionLangsChunk1( SyntaxDefinitionManager* sdm ) { { "%.gd$" }, } ); + sdm->addPreDefinition( + { "Gemini", []() -> SyntaxDefinition& { return addGemini(); }, { "%.gmi$" } } ); + sdm->addPreDefinition( { "Gleam", []() -> SyntaxDefinition& { return addGleam(); }, { "%.gleam$" } } ); From f3e5bee80a1fb13d37040381b662946454c36343 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Lucas=20Golini?= Date: Fri, 3 Jul 2026 13:27:27 -0300 Subject: [PATCH 2/2] Fix body height invalidation issue killing performance. --- include/eepp/ui/uirichtext.hpp | 6 +-- src/eepp/ui/uirichtext.cpp | 24 +++++++----- src/tests/unit_tests/uihtml_tests.cpp | 54 +++++++++++++++++++++++++++ 3 files changed, 72 insertions(+), 12 deletions(-) diff --git a/include/eepp/ui/uirichtext.hpp b/include/eepp/ui/uirichtext.hpp index 8c178c372..ca89eb81f 100644 --- a/include/eepp/ui/uirichtext.hpp +++ b/include/eepp/ui/uirichtext.hpp @@ -256,9 +256,9 @@ class EE_API UIHTMLBody : public UIRichText { UIHTMLBody( const std::string& tag = "body" ); Float getLocalMinHeight() const; - void setDocumentContentMinHeight( const Float& height ); - void updateDocumentMinHeight(); - void updateDocumentContentMinHeightFromChildren(); + bool setDocumentContentMinHeight( const Float& height ); + bool updateDocumentMinHeight(); + bool updateDocumentContentMinHeightFromChildren(); virtual Uint32 onMessage( const NodeMessage* Msg ); }; diff --git a/src/eepp/ui/uirichtext.cpp b/src/eepp/ui/uirichtext.cpp index 9c4c62e8c..d3219048b 100644 --- a/src/eepp/ui/uirichtext.cpp +++ b/src/eepp/ui/uirichtext.cpp @@ -291,27 +291,33 @@ Float UIHTMLBody::getLocalMinHeight() const { return convertLengthAsDp( mMinHeightLocal, parentHeight ); } -void UIHTMLBody::setDocumentContentMinHeight( const Float& height ) { +bool UIHTMLBody::setDocumentContentMinHeight( const Float& height ) { if ( mDocumentContentMinHeight == height ) - return; + return false; mDocumentContentMinHeight = height; - updateDocumentMinHeight(); + return updateDocumentMinHeight(); } -void UIHTMLBody::updateDocumentMinHeight() { +bool UIHTMLBody::updateDocumentMinHeight() { const Float oldMinHeight = getCurMinSize().getHeight(); Float minHeight = std::max( { getLocalMinHeight(), mDocumentViewportMinHeight, mDocumentContentMinHeight } ); setMinHeight( minHeight ); + bool minHeightChanged = minHeight != oldMinHeight; + bool sizeForced = false; if ( minHeight < oldMinHeight && - getPixelsSize().getHeight() > PixelDensity::dpToPx( minHeight ) ) + getPixelsSize().getHeight() > PixelDensity::dpToPx( minHeight ) ) { // Lowering min-height does not shrink the current box by itself. Reapply size through // min/max fitting so the body can settle at the new floor. setPixelsSize( { getPixelsSize().getWidth(), 0 } ); + sizeForced = true; + } + + return minHeightChanged || sizeForced; } -void UIHTMLBody::updateDocumentContentMinHeightFromChildren() { +bool UIHTMLBody::updateDocumentContentMinHeightFromChildren() { Float maxH = 0; Node* child = mChild; @@ -331,7 +337,7 @@ void UIHTMLBody::updateDocumentContentMinHeightFromChildren() { child = child->getNextNode(); } - setDocumentContentMinHeight( maxH > 0 ? std::trunc( PixelDensity::pxToDp( maxH ) ) : 0 ); + return setDocumentContentMinHeight( maxH > 0 ? std::trunc( PixelDensity::pxToDp( maxH ) ) : 0 ); } Uint32 UIHTMLBody::onMessage( const NodeMessage* Msg ) { @@ -340,10 +346,10 @@ Uint32 UIHTMLBody::onMessage( const NodeMessage* Msg ) { if ( Msg->getMsg() == NodeMessage::LayoutAttributeChange && Msg->getSender() != this && !mSettingBodyHeight ) { mSettingBodyHeight = true; - updateDocumentContentMinHeightFromChildren(); + bool documentMinHeightChanged = updateDocumentContentMinHeightFromChildren(); mSettingBodyHeight = false; - if ( getParent() && getParent()->isType( UI_TYPE_HTML_HTML ) ) + if ( documentMinHeightChanged && getParent() && getParent()->isType( UI_TYPE_HTML_HTML ) ) getParent()->asType()->setLayoutDirty( LayoutInvalidation::Document ); } diff --git a/src/tests/unit_tests/uihtml_tests.cpp b/src/tests/unit_tests/uihtml_tests.cpp index fbc29f507..859c4424c 100644 --- a/src/tests/unit_tests/uihtml_tests.cpp +++ b/src/tests/unit_tests/uihtml_tests.cpp @@ -4173,6 +4173,60 @@ UTEST( UIHTML, BodyDocumentContentMinHeightCanShrink ) { Engine::destroySingleton(); } +UTEST( UIHTML, BodyNoOpContentHeightChangeDoesNotDirtyHtml ) { + auto win = Engine::instance()->createWindow( + WindowSettings( 800, 600, "body no-op content height change", WindowStyle::Default, + WindowBackend::Default, 32, {}, 1, false, true ), + ContextSettings( false, 0, 0, GLv_default, true, false ) ); + + UISceneNode* sceneNode = init_test_inline_block(); + + UIWebView* webView = UIWebView::New(); + webView->setParent( sceneNode->getRoot() ); + webView->setPixelsSize( win->getWidth(), win->getHeight() ); + webView->setLayoutSizePolicy( SizePolicy::Fixed, SizePolicy::Fixed ); + + std::string html = R"html( + + + +
+ + + )html"; + + sceneNode->setURI( "file://body-no-op-content-height-change.html" ); + sceneNode->loadLayoutFromString( HTMLFormatter::HTMLtoXML( html ), + webView->getDocumentContainer(), + String::hash( "body-no-op-content-height-change" ) ); + webView->refreshDocumentLayout(); + + win->getInput()->update(); + SceneManager::instance()->update(); + sceneNode->updateDirtyLayouts(); + win->getInput()->update(); + SceneManager::instance()->update(); + sceneNode->updateDirtyLayouts(); + + auto* htmlNode = sceneNode->getRoot()->findByType( UI_TYPE_HTML_HTML )->asType(); + auto* body = sceneNode->getRoot()->findByType( UI_TYPE_HTML_BODY )->asType(); + auto* spacer = sceneNode->getRoot()->find( "spacer" )->asType(); + ASSERT_TRUE( htmlNode != nullptr ); + ASSERT_TRUE( body != nullptr ); + ASSERT_TRUE( spacer != nullptr ); + ASSERT_GT( body->getPixelsSize().getHeight(), 850.f ); + ASSERT_FALSE( htmlNode->isLayoutDirty() ); + + spacer->setPixelsSize( + { spacer->getPixelsSize().getWidth() + 25.f, spacer->getPixelsSize().getHeight() } ); + EXPECT_FALSE( htmlNode->isLayoutDirty() ); + win->getInput()->update(); + SceneManager::instance()->update(); + sceneNode->updateDirtyLayouts(); + + Engine::destroySingleton(); +} + UTEST( UIHTML, DeferredCSSKeepsTableHeightStableAfterViewportResize ) { std::shared_ptr threadPool( ThreadPool::createShared( eemax( 4, Sys::getCPUCount() ) ) );