From c7c45a87cfca3c57087b9768e51a847a26f416e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Lucas=20Golini?= Date: Sat, 29 Mar 2025 02:17:56 -0300 Subject: [PATCH] Fixes in UICodeEditor mouse click in gutter. Added some basic culling in UICodeEditor. Remember the Chat UI input text. --- include/eepp/scene/event.hpp | 1 + include/eepp/ui/doc/documentview.hpp | 3 ++ include/eepp/ui/uicodeeditor.hpp | 5 +++ src/eepp/ui/doc/documentview.cpp | 5 +++ src/eepp/ui/uicodeeditor.cpp | 42 ++++++++++++++++--- .../plugins/aiassistant/aiassistantplugin.cpp | 12 ++++-- .../ecode/plugins/aiassistant/chatui.cpp | 24 +++++++---- .../ecode/plugins/aiassistant/chatui.hpp | 4 +- 8 files changed, 79 insertions(+), 17 deletions(-) diff --git a/include/eepp/scene/event.hpp b/include/eepp/scene/event.hpp index dd90accb4..361fe076e 100644 --- a/include/eepp/scene/event.hpp +++ b/include/eepp/scene/event.hpp @@ -118,6 +118,7 @@ class EE_API Event { OnWindowToFront, OnVisibleLinesCountChange, OnDataChanged, + OnFoldUnfoldRange, NoEvent = eeINDEX_NOT_FOUND }; diff --git a/include/eepp/ui/doc/documentview.hpp b/include/eepp/ui/doc/documentview.hpp index 33559420f..cd5e0b0b7 100644 --- a/include/eepp/ui/doc/documentview.hpp +++ b/include/eepp/ui/doc/documentview.hpp @@ -157,6 +157,8 @@ class EE_API DocumentView { void setOnVisibleLineCountChange( std::function onVisibleLinesCountChangeCb ); + void setOnFoldUnfoldCb( std::function onFoldUnfoldCb ); + protected: std::shared_ptr mDoc; FontStyleConfig mFontStyle; @@ -171,6 +173,7 @@ class EE_API DocumentView { bool mUnderConstruction{ false }; bool mUpdatingFoldRegions{ false }; std::function mOnVisibleLineCountChange; + std::function mOnFoldUnfoldCb; void changeVisibility( Int64 fromDocIdx, Int64 toDocIdx, bool visible, bool recomputeOffset = true, bool recomputeLineToVisibleIndex = true ); diff --git a/include/eepp/ui/uicodeeditor.hpp b/include/eepp/ui/uicodeeditor.hpp index f14f51663..4a5ec2ee8 100644 --- a/include/eepp/ui/uicodeeditor.hpp +++ b/include/eepp/ui/uicodeeditor.hpp @@ -792,6 +792,10 @@ class EE_API UICodeEditor : public UIWidget, public TextDocument::Client { bool isCursorBlinkingAfterAMinuteOfInactivityDisabled() const; + void setAllowSelectingTextFromGutter( bool allow ); + + bool allowSelectingTextFromGutter() const; + protected: struct LastXOffset { TextPosition position{ 0, 0 }; @@ -841,6 +845,7 @@ class EE_API UICodeEditor : public UIWidget, public TextDocument::Client { bool mFoldsIsFirst{ true }; bool mEnableFlashCursor{ false }; bool mDisableCursorBlinkingAfterAMinuteOfInactivity{ true }; + bool mAllowSelectingTextFromGutter{ true }; Uint32 mTabWidth; std::atomic mHighlightWordProcessing{ false }; TextRange mLinkPosition; diff --git a/src/eepp/ui/doc/documentview.cpp b/src/eepp/ui/doc/documentview.cpp index ca328821c..0504db408 100644 --- a/src/eepp/ui/doc/documentview.cpp +++ b/src/eepp/ui/doc/documentview.cpp @@ -814,4 +814,9 @@ void DocumentView::setOnVisibleLineCountChange( mOnVisibleLineCountChange = std::move( onVisibleLinesCountChangeCb ); } +void DocumentView::setOnFoldUnfoldCb( + std::function onFoldUnfoldCb ) { + mOnFoldUnfoldCb = std::move( onFoldUnfoldCb ); +} + }}} // namespace EE::UI::Doc diff --git a/src/eepp/ui/uicodeeditor.cpp b/src/eepp/ui/uicodeeditor.cpp index b21cbb33a..a0e72be61 100644 --- a/src/eepp/ui/uicodeeditor.cpp +++ b/src/eepp/ui/uicodeeditor.cpp @@ -140,6 +140,8 @@ UICodeEditor::UICodeEditor( const std::string& elementTag, const bool& autoRegis refreshTag(); mDocView.setOnVisibleLineCountChange( [this] { sendCommonEvent( Event::OnVisibleLinesCountChange ); } ); + mDocView.setOnFoldUnfoldCb( + [this]( auto, auto ) { sendCommonEvent( Event::OnFoldUnfoldRange ); } ); mVScrollBar = UIScrollBar::NewVertical(); mVScrollBar->setParent( this ); mVScrollBar->addEventListener( Event::OnSizeChange, @@ -338,6 +340,13 @@ void UICodeEditor::draw() { drawLineEndings( lineRange, startScroll, lineHeight ); } + const bool canCull = !isMeOrParentTreeScaledOrRotated(); + const auto maxScreenY = isClipped() + ? eemin( getUISceneNode()->getPixelsSize().getHeight(), + mScreenPos.y + mSize.getHeight() ) + + lineHeight + : getUISceneNode()->getPixelsSize().getHeight() + lineHeight; + for ( auto i = lineRange.first; i <= lineRange.second; i++ ) { if ( !mDocView.isLineVisible( i ) ) continue; @@ -346,6 +355,14 @@ void UICodeEditor::draw() { { startScroll.x, static_cast( startScroll.y + mDocView.getLineYOffset( i, lineHeight ) ) } ); + if ( canCull ) { + if ( curScroll.y < -lineHeight && !mDocView.isWrappedLine( i ) ) + continue; + + if ( curScroll.y > maxScreenY ) + break; + } + for ( auto& plugin : mPlugins ) plugin->drawBeforeLineText( this, i, curScroll, charSize, lineHeight ); @@ -1597,8 +1614,9 @@ Uint32 UICodeEditor::onMouseDown( const Vector2i& position, const Uint32& flags mLastDoubleClick.getElapsedTime() < Milliseconds( 300.f ) ) { mDoc->selectLine(); } else { - mDoc->setSelection( textScreenPos ); - if ( downOverGutter ) + if ( !downOverGutter || mAllowSelectingTextFromGutter ) + mDoc->setSelection( textScreenPos ); + if ( downOverGutter && mAllowSelectingTextFromGutter ) mDoc->selectLine(); } } else if ( !mDoc->hasSelection() ) { @@ -1662,10 +1680,11 @@ Uint32 UICodeEditor::onMouseMove( const Vector2i& position, const Uint32& flags } Vector2f localPos( convertToNodeSpace( position.asFloat() ) ); + bool downOverGutter = localPos.x < mPaddingPx.Left + getGutterWidth(); if ( !mMouseDownMinimap && isTextSelectionEnabled() && - !getUISceneNode()->getEventDispatcher()->isNodeDragging() && NULL != mFont && - mMouseDown ) { + !getUISceneNode()->getEventDispatcher()->isNodeDragging() && NULL != mFont && mMouseDown && + ( !downOverGutter || mAllowSelectingTextFromGutter ) ) { TextRange selection = mDoc->getSelection(); selection.setStart( resolveScreenPosition( position.asFloat() ) ); mDoc->setSelection( selection ); @@ -1794,7 +1813,9 @@ Uint32 UICodeEditor::onMouseDoubleClick( const Vector2i& position, const Uint32& return 1; } - if ( isTextSelectionEnabled() && ( flags & EE_BUTTON_LMASK ) ) { + Vector2f localPos( convertToNodeSpace( position.asFloat() ) ); + if ( isTextSelectionEnabled() && ( flags & EE_BUTTON_LMASK ) && + localPos.x >= mPaddingPx.Left + getGutterWidth() && localPos.y >= mPluginsTopSpace ) { mDoc->selectWord( false ); mLastDoubleClick.restart(); checkColorPickerAction(); @@ -5254,7 +5275,8 @@ bool UICodeEditor::isNotMonospace() const { void UICodeEditor::updateMouseCursor( const Vector2f& position ) { if ( getScreenBounds().contains( position ) ) { auto localPos( convertToNodeSpace( position ) ); - bool overGutterOrTop = localPos.x < getGutterWidth() || localPos.y < mPluginsTopSpace; + bool overGutterOrTop = + localPos.x < mPaddingPx.Left + getGutterWidth() || localPos.y < mPluginsTopSpace; getUISceneNode()->setCursor( mHandShown ? Cursor::Hand : ( !overGutterOrTop && !mLocked ? Cursor::IBeam : Cursor::Arrow ) ); @@ -5315,4 +5337,12 @@ bool UICodeEditor::isCursorBlinkingAfterAMinuteOfInactivityDisabled() const { return mDisableCursorBlinkingAfterAMinuteOfInactivity; } +void UICodeEditor::setAllowSelectingTextFromGutter( bool allow ) { + mAllowSelectingTextFromGutter = allow; +} + +bool UICodeEditor::allowSelectingTextFromGutter() const { + return mAllowSelectingTextFromGutter; +} + }} // namespace EE::UI diff --git a/src/tools/ecode/plugins/aiassistant/aiassistantplugin.cpp b/src/tools/ecode/plugins/aiassistant/aiassistantplugin.cpp index e6a248a89..2690c08b1 100644 --- a/src/tools/ecode/plugins/aiassistant/aiassistantplugin.cpp +++ b/src/tools/ecode/plugins/aiassistant/aiassistantplugin.cpp @@ -186,17 +186,23 @@ void AIAssistantPlugin::load( PluginManager* pluginManager ) { filePath = conversationsPath + *foundIt; } + std::string inputText; if ( !filePath.empty() ) { std::string data; FileSystem::fileGet( filePath, data ); if ( !data.empty() ) { auto j = nlohmann::json::parse( data, nullptr, false ); - if ( !j.empty() ) - chatUI->unserialize( j ); + if ( !j.empty() ){ + inputText = chatUI->unserialize( j ); + } } } - chatUI->on( Event::OnDataChanged, [chatUI]( auto ) { chatUI->updateTabTitle(); } ); + chatUI->on( Event::OnDataChanged, [chatUI, inputText = std::move( inputText )]( auto ) { + chatUI->updateTabTitle(); + if ( chatUI->getChatInput() ) + chatUI->getChatInput()->getDocument().textInput( inputText ); + } ); } return TabWidgetData{ chatUI, getPluginContext()->findIcon( "code-ai" ), diff --git a/src/tools/ecode/plugins/aiassistant/chatui.cpp b/src/tools/ecode/plugins/aiassistant/chatui.cpp index ddca91811..9421ed0b1 100644 --- a/src/tools/ecode/plugins/aiassistant/chatui.cpp +++ b/src/tools/ecode/plugins/aiassistant/chatui.cpp @@ -138,7 +138,7 @@ DropDownList.role_ui { - + @@ -220,6 +220,7 @@ LLMChatUI::LLMChatUI( PluginManager* manager ) : mChatInput->getDocument().getFoldRangeService().setEnabled( true ); mChatInput->setFoldDrawable( findIcon( "chevron-down", PixelDensity::dpToPxI( 12 ) ) ); mChatInput->setFoldedDrawable( findIcon( "chevron-right", PixelDensity::dpToPxI( 12 ) ) ); + mChatInput->setAllowSelectingTextFromGutter( false ); mChatInput->setSyntaxDefinition( markdown ); @@ -311,10 +312,12 @@ LLMChatUI::LLMChatUI( PluginManager* manager ) : return; } auto* chatUI = getPlugin()->newAIAssistant(); - chatUI->unserialize( serialize() ); + auto input = chatUI->unserialize( serialize() ); chatUI->mUUID = UUID(); chatUI->mSummary += i18n( "chat_cloned", " (cloned)" ); - updateTabTitle(); + if ( !input.empty() ) + chatUI->mChatInput->getDocument().textInput( input ); + chatUI->updateTabTitle(); chatUI->setFocus(); } ); @@ -517,7 +520,9 @@ void LLMChatUI::showChatHistory() { std::string data; FileSystem::fileGet( path.toString(), data ); nlohmann::json j = nlohmann::json::parse( data, nullptr, false ); - chatUI->unserialize( j ); + auto input = chatUI->unserialize( j ); + if ( !input.empty() ) + chatUI->mChatInput->getDocument().textInput( input ); chatUI->setFocus(); }; @@ -737,10 +742,12 @@ nlohmann::json LLMChatUI::serialize() { j["provider"] = mCurModel.provider; j["timestamp"] = mTimestamp; j["summary"] = mSummary; + std::string inputText( mChatInput->getDocument().getText().toUtf8() ); + j["input"] = std::move( inputText ); return j; } -void LLMChatUI::unserialize( const nlohmann::json& payload ) { +std::string LLMChatUI::unserialize( const nlohmann::json& payload ) { auto uuid = UUID::fromString( payload.value( "uuid", "" ) ); if ( uuid ) mUUID = *uuid; @@ -755,7 +762,7 @@ void LLMChatUI::unserialize( const nlohmann::json& payload ) { } if ( mCurModel.name.empty() ) - return; + return payload.value( "input", "" ); if ( !selectModel( mModelDDL, mCurModel ) ) fillModelDropDownList( mModelDDL ); @@ -770,6 +777,8 @@ void LLMChatUI::unserialize( const nlohmann::json& payload ) { } updateTabTitle(); + + return payload.value( "input", "" ); } LLMModel LLMChatUI::findModel( const std::string& provider, const std::string& model ) { @@ -843,7 +852,7 @@ void LLMChatUI::doRequest() { auto* thinking = editor->findByClass( "thinking" ); auto thinkingID = String::hash( String::format( "thinking-%p", thinking ) ); thinking->setVisible( true ); - thinking->setPosition( { PixelDensity::dpToPx( 16 ), PixelDensity::dpToPx( 4 ) } ); + thinking->setPosition( { PixelDensity::dpToPx( 8 ), PixelDensity::dpToPx( 4 ) } ); thinking->setInterval( [thinking] { thinking->rotate( 360 / 32 ); }, Seconds( 0.125 ), thinkingID ); @@ -1035,6 +1044,7 @@ void LLMChatUI::addChat( LLMChat::Role role, std::string conversation ) { auto* editor = chat->findByClass( "data_ui" ); editor->getDocument().textInput( String::fromUtf8( conversation ) ); editor->setCursorVisible( false ); + editor->setAllowSelectingTextFromGutter( false ); resizeToFit( editor ); } diff --git a/src/tools/ecode/plugins/aiassistant/chatui.hpp b/src/tools/ecode/plugins/aiassistant/chatui.hpp index 9dc48d1c9..e5e983890 100644 --- a/src/tools/ecode/plugins/aiassistant/chatui.hpp +++ b/src/tools/ecode/plugins/aiassistant/chatui.hpp @@ -52,7 +52,7 @@ class LLMChatUI : public UILinearLayout, public WidgetCommandExecuter { nlohmann::json serialize(); - void unserialize( const nlohmann::json& payload ); + std::string unserialize( const nlohmann::json& payload ); // returns the input value UISplitter* getSplitter() const; @@ -78,6 +78,8 @@ class LLMChatUI : public UILinearLayout, public WidgetCommandExecuter { return WidgetCommandExecuter::onKeyDown( event ); } + UICodeEditor* getChatInput() const { return mChatInput; } + protected: UUID mUUID; std::string mSummary;