diff --git a/include/eepp/ui/doc/foldrangeservice.hpp b/include/eepp/ui/doc/foldrangeservice.hpp index be829c31b..8253591b9 100644 --- a/include/eepp/ui/doc/foldrangeservice.hpp +++ b/include/eepp/ui/doc/foldrangeservice.hpp @@ -44,11 +44,16 @@ class EE_API FoldRangeServive { void setProvider( const FoldRangeProvider& provider ); + bool isEnabled() const; + + void setEnabled( bool enabled ); + protected: TextDocument* mDoc; std::unordered_map mFoldingRegions; FoldRangeProvider mProvider{ nullptr }; Mutex mMutex; + bool mEnabled{ true }; }; }}} // namespace EE::UI::Doc diff --git a/include/eepp/ui/uicodeeditor.hpp b/include/eepp/ui/uicodeeditor.hpp index 0d390673c..c0b3f220d 100644 --- a/include/eepp/ui/uicodeeditor.hpp +++ b/include/eepp/ui/uicodeeditor.hpp @@ -701,15 +701,26 @@ class EE_API UICodeEditor : public UIWidget, public TextDocument::Client { Float getMinimapLineSpacing() const; bool getShowFoldingRegion() const; + void setShowFoldingRegion( bool showFoldingRegion ); Drawable* getFoldDrawable() const; - void setFoldDrawable(Drawable* foldDrawable); + + void setFoldDrawable( Drawable* foldDrawable ); Drawable* getFoldedDrawable() const; - void setFoldedDrawable(Drawable* foldedDrawable); - protected: + void setFoldedDrawable( Drawable* foldedDrawable ); + + bool getFoldsAlwaysVisible() const; + + void setFoldsAlwaysVisible( bool foldsAlwaysVisible ); + + Time getFoldsRefreshTime() const; + + void setFoldsRefreshTime( const Time& foldsRefreshTime ); + + protected: struct LastXOffset { TextPosition position{ 0, 0 }; Float offset{ 0.f }; @@ -720,6 +731,7 @@ class EE_API UICodeEditor : public UIWidget, public TextDocument::Client { DocumentView mDocView; Clock mBlinkTimer; Time mBlinkTime; + Time mFoldsRefreshTime; bool mDirtyEditor{ false }; bool mDirtyScroll{ false }; bool mCursorVisible{ false }; @@ -751,6 +763,7 @@ class EE_API UICodeEditor : public UIWidget, public TextDocument::Client { bool mDisplayLockedIcon{ false }; bool mInvalidateOnLoaded{ false }; bool mUseDefaultStyle{ false }; + bool mFoldsAlwaysVisible{ false }; bool mFoldsVisible{ false }; std::atomic mHighlightWordProcessing{ false }; TextRange mLinkPosition; diff --git a/include/eepp/ui/uipushbutton.hpp b/include/eepp/ui/uipushbutton.hpp index 5ce41a7c0..21c694ae3 100644 --- a/include/eepp/ui/uipushbutton.hpp +++ b/include/eepp/ui/uipushbutton.hpp @@ -61,7 +61,7 @@ class EE_API UIPushButton : public UIWidget { virtual std::vector getPropertiesImplemented() const; - void setTextAlign( const Uint32& align ); + UIWidget* setTextAlign( const Uint32& align ); virtual Sizef getContentSize() const; diff --git a/src/eepp/ui/doc/foldrangeservice.cpp b/src/eepp/ui/doc/foldrangeservice.cpp index 3e3ec1804..eb5b932f9 100644 --- a/src/eepp/ui/doc/foldrangeservice.cpp +++ b/src/eepp/ui/doc/foldrangeservice.cpp @@ -100,6 +100,8 @@ static std::vector findFoldingRangesIndentation( TextDocument* doc ) FoldRangeServive::FoldRangeServive( TextDocument* doc ) : mDoc( doc ) {} bool FoldRangeServive::canFold() const { + if ( !mEnabled ) + return false; if ( mProvider && mProvider( mDoc, false ) ) return true; auto type = mDoc->getSyntaxDefinition().getFoldRangeType(); @@ -107,7 +109,7 @@ bool FoldRangeServive::canFold() const { } void FoldRangeServive::findRegions() { - if ( mDoc == nullptr || !canFold() ) + if ( !mEnabled || mDoc == nullptr || !canFold() ) return; if ( mProvider && mProvider( mDoc, true ) ) @@ -197,4 +199,12 @@ void FoldRangeServive::setProvider( const FoldRangeProvider& provider ) { } } +bool FoldRangeServive::isEnabled() const { + return mEnabled; +} + +void FoldRangeServive::setEnabled( bool enabled ) { + mEnabled = enabled; +} + }}} // namespace EE::UI::Doc diff --git a/src/eepp/ui/uicodeeditor.cpp b/src/eepp/ui/uicodeeditor.cpp index ba9faf6ab..4edae2668 100644 --- a/src/eepp/ui/uicodeeditor.cpp +++ b/src/eepp/ui/uicodeeditor.cpp @@ -121,6 +121,7 @@ UICodeEditor::UICodeEditor( const std::string& elementTag, const bool& autoRegis mDoc( std::make_shared() ), mDocView( mDoc, mFontStyleConfig, {} ), mBlinkTime( Seconds( 0.5f ) ), + mFoldsRefreshTime( Seconds( 2.f ) ), mTabWidth( 4 ), mMouseWheelScroll( 50 ), mFontSize( mFontStyleConfig.getFontCharacterSize() ), @@ -1479,7 +1480,7 @@ Uint32 UICodeEditor::onMouseMove( const Vector2i& position, const Uint32& flags checkMouseOverLink( position ); } - if ( mShowFoldingRegion ) { + if ( mShowFoldingRegion && !mFoldsAlwaysVisible ) { Vector2f localPos( convertToNodeSpace( position.asFloat() ) ); bool oldFoldVisible = mFoldsVisible; mFoldsVisible = localPos.x <= mPaddingPx.Left + getGutterWidth(); @@ -3766,7 +3767,8 @@ void UICodeEditor::drawLineNumbers( const DocumentLineRange& lineRange, const Ve Float w = 0.f; if ( mShowLineNumber ) w += lineNumberWidth; - if ( mShowFoldingRegion && mDoc->getFoldRangeService().canFold() ) + bool foldVisible = mShowFoldingRegion && mDoc->getFoldRangeService().canFold(); + if ( foldVisible ) w += mFoldRegionWidth; primitives.drawRectangle( Rectf( screenStart, Sizef( w, mSize.getHeight() ) ) ); TextRange selection = mDoc->getSelection( true ); @@ -3796,10 +3798,10 @@ void UICodeEditor::drawLineNumbers( const DocumentLineRange& lineRange, const Ve mFontStyleConfig.ShadowOffset ); } - if ( mShowFoldingRegion && mDoc->getFoldRangeService().isFoldingRegionInLine( i ) ) { + if ( foldVisible && mDoc->getFoldRangeService().isFoldingRegionInLine( i ) ) { bool isFolded = mDocView.isFolded( i ); - if ( mFoldsVisible ) { + if ( mFoldsAlwaysVisible || mFoldsVisible ) { if ( ( isFolded && mFoldedDrawable ) || ( !isFolded && mFoldedDrawable ) ) { Drawable* drawable = isFolded ? mFoldedDrawable : mFoldDrawable; GlyphDrawable::DrawMode oldMode; @@ -4657,6 +4659,22 @@ void UICodeEditor::setFoldedDrawable( Drawable* foldedDrawable ) { mFoldedDrawable = foldedDrawable; } +bool UICodeEditor::getFoldsAlwaysVisible() const { + return mFoldsAlwaysVisible; +} + +void UICodeEditor::setFoldsAlwaysVisible( bool foldsAlwaysVisible ) { + mFoldsAlwaysVisible = foldsAlwaysVisible; +} + +Time UICodeEditor::getFoldsRefreshTime() const { + return mFoldsRefreshTime; +} + +void UICodeEditor::setFoldsRefreshTime( const Time& foldsRefreshTime ) { + mFoldsRefreshTime = foldsRefreshTime; +} + bool UICodeEditor::isMinimapFileTooLarge() const { return mDocView.getVisibleLinesCount() > 1 && mDocView.getVisibleLinesCount() > @@ -4781,9 +4799,8 @@ void UICodeEditor::findRegionsDelayed() { return; UISceneNode* sceneNode = getUISceneNode(); if ( sceneNode ) { - sceneNode->removeActionsByTag( mTagFoldRange ); - sceneNode->runOnMainThread( [this]() { mDoc->getFoldRangeService().findRegions(); }, - Seconds( 1.f ), mTagFoldRange ); + sceneNode->debounce( [this]() { mDoc->getFoldRangeService().findRegions(); }, + mFoldsRefreshTime, mTagFoldRange ); } } diff --git a/src/eepp/ui/uipushbutton.cpp b/src/eepp/ui/uipushbutton.cpp index bc70e7033..617e9f2e4 100644 --- a/src/eepp/ui/uipushbutton.cpp +++ b/src/eepp/ui/uipushbutton.cpp @@ -469,10 +469,11 @@ UIWidget* UIPushButton::getExtraInnerWidget() const { return NULL; } -void UIPushButton::setTextAlign( const Uint32& align ) { +UIWidget* UIPushButton::setTextAlign( const Uint32& align ) { mFlags &= ~( UI_HALIGN_CENTER | UI_HALIGN_RIGHT ); mFlags |= align; onAlignChange(); + return this; } Sizef UIPushButton::getContentSize() const { diff --git a/src/tools/ecode/appconfig.cpp b/src/tools/ecode/appconfig.cpp index 3704a577f..9a0ec4e2a 100644 --- a/src/tools/ecode/appconfig.cpp +++ b/src/tools/ecode/appconfig.cpp @@ -145,6 +145,12 @@ void AppConfig::load( const std::string& confPath, std::string& keybindingsPath, DocumentView::toLineWrapType( ini.getValue( "editor", "wrap_type", "viewport" ) ); editor.wrapKeepIndentation = ini.getValueB( "editor", "wrap_keep_indentation", true ); + editor.codeFoldingEnabled = ini.getValueB( "editor", "code_folding_enabled", true ); + editor.codeFoldingAlwaysVisible = + ini.getValueB( "editor", "code_folding_always_visible", false ); + editor.codeFoldingRefreshFreq = + Time::fromString( ini.getValue( "editor", "code_folding_refresh_frequency", "2s" ) ); + searchBarConfig.caseSensitive = ini.getValueB( "search_bar", "case_sensitive", false ); searchBarConfig.luaPattern = ini.getValueB( "search_bar", "lua_pattern", false ); searchBarConfig.wholeWord = ini.getValueB( "search_bar", "whole_word", false ); @@ -274,6 +280,11 @@ void AppConfig::save( const std::vector& recentFiles, ini.setValue( "editor", "wrap_type", DocumentView::fromLineWrapType( editor.wrapType ) ); ini.setValueB( "editor", "wrap_keep_indentation", editor.wrapKeepIndentation ); + ini.setValueB( "editor", "code_folding_enabled", editor.codeFoldingEnabled ); + ini.setValueB( "editor", "code_folding_always_visible", editor.codeFoldingAlwaysVisible ); + ini.setValue( "editor", "code_folding_refresh_frequency", + editor.codeFoldingRefreshFreq.toString() ); + ini.setValueB( "search_bar", "case_sensitive", searchBarConfig.caseSensitive ); ini.setValueB( "search_bar", "lua_pattern", searchBarConfig.luaPattern ); ini.setValueB( "search_bar", "whole_word", searchBarConfig.wholeWord ); diff --git a/src/tools/ecode/appconfig.hpp b/src/tools/ecode/appconfig.hpp index aa58ee6b2..d190c449f 100644 --- a/src/tools/ecode/appconfig.hpp +++ b/src/tools/ecode/appconfig.hpp @@ -76,11 +76,14 @@ struct CodeEditorConfig { bool autoCloseXMLTags{ true }; bool linesRelativePosition{ false }; bool autoReloadOnDiskChange{ false }; + bool codeFoldingEnabled{ true }; + bool codeFoldingAlwaysVisible{ false }; LineWrapMode wrapMode{ LineWrapMode::NoWrap }; LineWrapType wrapType{ LineWrapType::Viewport }; bool wrapKeepIndentation{ true }; std::string autoCloseBrackets{ "" }; Time cursorBlinkingTime{ Seconds( 0.5f ) }; + Time codeFoldingRefreshFreq{ Seconds( 2.f ) }; }; struct DocumentConfig { diff --git a/src/tools/ecode/ecode.cpp b/src/tools/ecode/ecode.cpp index 109822fbb..78e85931c 100644 --- a/src/tools/ecode/ecode.cpp +++ b/src/tools/ecode/ecode.cpp @@ -1313,6 +1313,30 @@ void App::setCursorBlinkingTime() { setFocusEditorOnClose( msgBox ); } +void App::setFoldRefreshFreq() { + UIMessageBox* msgBox = UIMessageBox::New( + UIMessageBox::INPUT, + i18n( "set_fold_refresh_frequency", + "Set code folds refresh frequency:\nIt should be bigger than 1 second.\nFolds are " + "only refreshed after any document modification." ) + .unescape() ); + msgBox->setTitle( mWindowTitle ); + msgBox->setCloseShortcut( { KEY_ESCAPE, 0 } ); + msgBox->getTextInput()->setText( mConfig.editor.codeFoldingRefreshFreq.toString() ); + msgBox->showWhenReady(); + msgBox->on( Event::OnConfirm, [this, msgBox]( const Event* ) { + mConfig.editor.codeFoldingRefreshFreq = + Time::fromString( msgBox->getTextInput()->getText().toUtf8() ); + if ( mConfig.editor.codeFoldingRefreshFreq < Seconds( 1 ) ) + mConfig.editor.codeFoldingRefreshFreq = Seconds( 1 ); + mSplitter->forEachEditor( [this]( UICodeEditor* editor ) { + editor->setFoldsRefreshTime( mConfig.editor.codeFoldingRefreshFreq ); + } ); + msgBox->closeWindow(); + } ); + setFocusEditorOnClose( msgBox ); +} + void App::loadFileFromPathOrFocus( const std::string& path ) { UITab* tab = mSplitter->isDocumentOpen( path ); if ( !tab ) { @@ -2535,6 +2559,10 @@ void App::onCodeEditorCreated( UICodeEditor* editor, TextDocument& doc ) { doc.setAutoDetectIndentType( docc.autoDetectIndentType ); doc.setBOM( docc.writeUnicodeBOM ); + doc.getFoldRangeService().setEnabled( config.codeFoldingEnabled ); + editor->setFoldsAlwaysVisible( config.codeFoldingAlwaysVisible ); + editor->setFoldsRefreshTime( config.codeFoldingRefreshFreq ); + editor->addKeyBinds( getLocalKeybindings() ); editor->addUnlockedCommands( getUnlockedCommands() ); doc.setCommand( "save-doc", [this] { saveDoc(); } ); diff --git a/src/tools/ecode/ecode.hpp b/src/tools/ecode/ecode.hpp index c2f10d033..7ea801572 100644 --- a/src/tools/ecode/ecode.hpp +++ b/src/tools/ecode/ecode.hpp @@ -334,6 +334,8 @@ class App : public UICodeEditorSplitter::Client { void setCursorBlinkingTime(); + void setFoldRefreshFreq(); + void checkForUpdates( bool fromStartup = false ); void aboutEcode(); diff --git a/src/tools/ecode/iconmanager.cpp b/src/tools/ecode/iconmanager.cpp index 910032e8c..d17833cc7 100644 --- a/src/tools/ecode/iconmanager.cpp +++ b/src/tools/ecode/iconmanager.cpp @@ -248,6 +248,7 @@ void IconManager::init( UISceneNode* sceneNode, FontTrueType* iconFont, FontTrue { "tools", 0xeb6d }, { "play", 0xeb2c }, { "output", 0xeb9d }, + { "fold", 0xeaf5 }, }; for ( const auto& icon : codIcons ) diff --git a/src/tools/ecode/plugins/lsp/lspdocumentclient.cpp b/src/tools/ecode/plugins/lsp/lspdocumentclient.cpp index c82bb8b5f..5f082afcb 100644 --- a/src/tools/ecode/plugins/lsp/lspdocumentclient.cpp +++ b/src/tools/ecode/plugins/lsp/lspdocumentclient.cpp @@ -35,8 +35,6 @@ LSPDocumentClient::~LSPDocumentClient() { sceneNode->removeActionsByTag( mTag ); if ( nullptr != sceneNode && 0 != mTagSemanticTokens ) sceneNode->removeActionsByTag( mTagSemanticTokens ); - if ( nullptr != sceneNode && 0 != mTagFoldRange ) - sceneNode->removeActionsByTag( mTagFoldRange ); mShutdown = true; while ( mRunningSemanticTokens ) Sys::sleep( Milliseconds( 0.1f ) ); @@ -130,7 +128,6 @@ void LSPDocumentClient::refreshTag() { String::HashType oldTag = mTag; mTag = String::hash( mDoc->getURI().toString() ); mTagSemanticTokens = String::hash( mDoc->getURI().toString() + ":semantictokens" ); - mTagFoldRange = String::hash( mDoc->getURI().toString() + ":foldrange" ); UISceneNode* sceneNode = getUISceneNode(); if ( nullptr != sceneNode && 0 != oldTag ) sceneNode->removeActionsByTag( oldTag ); @@ -420,24 +417,6 @@ void LSPDocumentClient::requestFoldRange() { } } -void LSPDocumentClient::requestFoldRangeDelayed() { - if ( !mServer || !mServer->getCapabilities().foldingRangeProvider ) - return; - UISceneNode* sceneNode = getUISceneNode(); - if ( sceneNode ) { - sceneNode->removeActionsByTag( mTagFoldRange ); - LSPDocumentClient* docClient = this; - URI uri = mDoc->getURI(); - LSPClientServer* server = mServer; - sceneNode->runOnMainThread( - [docClient, server, uri]() { - if ( server->hasDocument( uri ) ) - docClient->requestFoldRange(); - }, - Seconds( 1.f ), mTagFoldRange ); - } -} - void LSPDocumentClient::requestSymbolsDelayed() { if ( !mServer || !mServer->getCapabilities().documentSymbolProvider ) return; diff --git a/src/tools/ecode/plugins/lsp/lspdocumentclient.hpp b/src/tools/ecode/plugins/lsp/lspdocumentclient.hpp index 071b3bb97..3b75c81ad 100644 --- a/src/tools/ecode/plugins/lsp/lspdocumentclient.hpp +++ b/src/tools/ecode/plugins/lsp/lspdocumentclient.hpp @@ -60,8 +60,6 @@ class LSPDocumentClient : public TextDocument::Client { void requestFoldRange(); - void requestFoldRangeDelayed(); - bool isRunningSemanticTokens() const; bool isWaitingSemanticTokensResponse() const; @@ -74,7 +72,6 @@ class LSPDocumentClient : public TextDocument::Client { TextDocument* mDoc{ nullptr }; String::HashType mTag{ 0 }; String::HashType mTagSemanticTokens{ 0 }; - String::HashType mTagFoldRange{ 0 }; int mVersion{ 0 }; std::string mSemanticeResultId; LSPSemanticTokensDelta mSemanticTokens; diff --git a/src/tools/ecode/settingsmenu.cpp b/src/tools/ecode/settingsmenu.cpp index c8db46a36..6bfd47aa4 100644 --- a/src/tools/ecode/settingsmenu.cpp +++ b/src/tools/ecode/settingsmenu.cpp @@ -123,6 +123,14 @@ void SettingsMenu::createSettingsMenu( App* app, UIMenuBar* menuBar ) { ->setId( "help-menu" ) ->asType(); + auto shiftHintBut = + mSettingsMenu->add( i18n( "menu_hold_shift_hint", "(Hold \"Shift\" to keep menu open)" ) ); + shiftHintBut->getTextBox()->setFontStyle( Text::Italic ); + shiftHintBut->setTextAlign( UI_HALIGN_CENTER ); + shiftHintBut->setTooltipText( i18n( + "menu_hold_shift_hint_desc", + "Keeping \"Shift\" clicked while changing any options it will keep the menu open." ) ); + mSettingsMenu->addSeparator(); mSettingsMenu ->add( i18n( "close", "Close" ), findIcon( "document-close" ), getKeybind( "close-tab" ) ) @@ -1318,6 +1326,68 @@ UIMenu* SettingsMenu::createViewMenu() { mViewMenu->addSeparator(); + mCodeFoldingMenu = UIPopUpMenu::New(); + + const auto codeFoldingMenuRefresh = [this] { + const auto& cfg = mApp->getConfig(); + + mCodeFoldingMenu->getItemId( "code_folding_enabled" ) + ->asType() + ->setActive( cfg.editor.codeFoldingEnabled ); + + mCodeFoldingMenu->getItemId( "code_folding_always_display_folds" ) + ->asType() + ->setActive( cfg.editor.codeFoldingAlwaysVisible ) + ->setEnabled( cfg.editor.codeFoldingEnabled ); + + mCodeFoldingMenu->getItemId( "folds_refresh_freq" ) + ->setEnabled( cfg.editor.codeFoldingEnabled ); + }; + + mViewMenu + ->addSubMenu( i18n( "code_folding", "Code Folding" ), findIcon( "fold" ), mCodeFoldingMenu ) + ->on( Event::OnMenuShow, [this, codeFoldingMenuRefresh]( auto ) { + if ( mCodeFoldingMenu->getCount() == 0 ) { + mCodeFoldingMenu->addCheckBox( i18n( "enabled", "Enabled" ) ) + ->setId( "code_folding_enabled" ); + + mCodeFoldingMenu + ->addCheckBox( + i18n( "code_folding_always_display_folds", "Folds always visible" ) ) + ->setId( "code_folding_always_display_folds" ); + + mCodeFoldingMenu->add( i18n( "folds_refresh_freq", "Folds Refresh Frequency" ) ) + ->setId( "folds_refresh_freq" ); + + mCodeFoldingMenu->on( + Event::OnItemClicked, [this, codeFoldingMenuRefresh]( const Event* event ) { + if ( !event->getNode()->isType( UI_TYPE_MENUITEM ) ) + return; + UIMenuItem* item = event->getNode()->asType(); + if ( "code_folding_enabled" == item->getId() ) { + bool enabled = item->asType()->isActive(); + mApp->getConfig().editor.codeFoldingEnabled = enabled; + mApp->getSplitter()->forEachDoc( [enabled]( TextDocument& doc ) { + doc.getFoldRangeService().setEnabled( enabled ); + } ); + codeFoldingMenuRefresh(); + } else if ( "code_folding_always_display_folds" == item->getId() ) { + bool enabled = item->asType()->isActive(); + mApp->getConfig().editor.codeFoldingAlwaysVisible = enabled; + mApp->getSplitter()->forEachEditor( [enabled]( UICodeEditor* editor ) { + editor->setFoldsAlwaysVisible( enabled ); + } ); + } else if ( "folds_refresh_freq" == item->getId() ) { + mApp->setFoldRefreshFreq(); + } + } ); + } + + codeFoldingMenuRefresh(); + } ); + + mViewMenu->addSeparator(); + mViewMenu->addCheckBox( i18n( "show_line_numbers", "Show Line Numbers" ) ) ->setActive( mApp->getConfig().editor.showLineNumbers ) ->setId( "show-line-numbers" ); diff --git a/src/tools/ecode/settingsmenu.hpp b/src/tools/ecode/settingsmenu.hpp index 17f76440d..739630482 100644 --- a/src/tools/ecode/settingsmenu.hpp +++ b/src/tools/ecode/settingsmenu.hpp @@ -112,6 +112,7 @@ class SettingsMenu { UIPopUpMenu* mEditMenu{ nullptr }; UIPopUpMenu* mHelpMenu{ nullptr }; UIPopUpMenu* mLineWrapMenu{ nullptr }; + UIPopUpMenu* mCodeFoldingMenu{ nullptr }; UIMenuBar* mMenuBar{ nullptr }; std::vector mFileTypeMenues; Float mFileTypeMenuesCreatedWithHeight{ 0 };