diff --git a/bin/assets/i18n/de.xml b/bin/assets/i18n/de.xml index b7ffded0e..98ef84fdc 100644 --- a/bin/assets/i18n/de.xml +++ b/bin/assets/i18n/de.xml @@ -1263,6 +1263,10 @@ Für sichtbare Änderung ecode neu starten. Aktualisieren Ältere Commits laden Noch keine Commits + Keine weiteren Commits Git-Verlauf konnte nicht geladen werden Wiederholen + Lokal + Remote + Tag diff --git a/bin/assets/i18n/en.xml b/bin/assets/i18n/en.xml index 14f519b2b..d4207b7e3 100644 --- a/bin/assets/i18n/en.xml +++ b/bin/assets/i18n/en.xml @@ -1248,6 +1248,10 @@ Restart ecode to see the changes. Refresh Load older commits No commits yet + No additional commits Could not load Git history Retry + Local + Remote + Tag diff --git a/bin/assets/i18n/fr.xml b/bin/assets/i18n/fr.xml index ba9c2c73d..8655312ca 100644 --- a/bin/assets/i18n/fr.xml +++ b/bin/assets/i18n/fr.xml @@ -1247,6 +1247,10 @@ Redémarrer ecode pour voir les changements. Actualiser Charger les commits précédents Aucun commit pour le moment + Aucun commit supplémentaire Impossible de charger l’historique Git Réessayer + Locale + Distante + Tag diff --git a/bin/assets/i18n/zh.xml b/bin/assets/i18n/zh.xml index c0aacb124..5f7812978 100644 --- a/bin/assets/i18n/zh.xml +++ b/bin/assets/i18n/zh.xml @@ -1052,6 +1052,10 @@ file in the directory tree. 刷新 加载更早的提交 尚无提交 + 没有其他提交 无法加载 Git 历史记录 重试 + 本地 + 远程 + 标签 diff --git a/include/eepp/scene/event.hpp b/include/eepp/scene/event.hpp index 16997a371..74768f063 100644 --- a/include/eepp/scene/event.hpp +++ b/include/eepp/scene/event.hpp @@ -134,6 +134,7 @@ class EE_API Event { OnApply, OnShowFindReplace, OnHideFindReplace, + OnTooltipCreated, NoEvent = eeINDEX_NOT_FOUND }; diff --git a/include/eepp/scene/mainthreadlifetime.hpp b/include/eepp/scene/mainthreadlifetime.hpp index 6621618a0..66eac3c38 100644 --- a/include/eepp/scene/mainthreadlifetime.hpp +++ b/include/eepp/scene/mainthreadlifetime.hpp @@ -30,7 +30,8 @@ template class MainThreadLifetime { public: WeakHandle() = default; - void run( std::function callback ) const { + void run( std::function callback, const Time& delay = Time::Zero, + Action::UniqueID tag = 0 ) const { auto state = mState.lock(); if ( !state ) return; @@ -49,7 +50,8 @@ template class MainThreadLifetime { } if ( object ) callback( object ); - } ); + }, + delay, tag ); } explicit operator bool() const { return !mState.expired(); } diff --git a/src/eepp/ui/tools/uiimageviewer.cpp b/src/eepp/ui/tools/uiimageviewer.cpp index c2e53e4d8..2350231ee 100644 --- a/src/eepp/ui/tools/uiimageviewer.cpp +++ b/src/eepp/ui/tools/uiimageviewer.cpp @@ -449,8 +449,20 @@ void UIImageViewer::updateTextDisplay() { if ( ( mDisplayOptions & DisplayOptions::DisplayDimensions ) && mImage->getDrawable() ) { str += i18n( "dimensions_colon", "Dimensions:" ); str += " "; - str += String::format( "%d x %d", (int)mImage->getDrawable()->getPixelsSize().x, - (int)mImage->getDrawable()->getPixelsSize().y ); + if ( mImage->getDrawable()->getDrawableType() == Drawable::Type::SPRITE ) { + auto ref = mImage->getDrawable(); + Sprite* spr = static_cast( ref.get() ); + if ( spr->getCurrentTextureRegion() ) { + auto tex = spr->getCurrentTextureRegion()->getTexture(); + if ( tex ) { + str += String::format( "%d x %d", (int)tex->getImageWidth(), + (int)tex->getImageHeight() ); + } + } + } else { + str += String::format( "%d x %d", (int)mImage->getDrawable()->getPixelsSize().x, + (int)mImage->getDrawable()->getPixelsSize().y ); + } str += "\n"; } diff --git a/src/eepp/ui/uiwidget.cpp b/src/eepp/ui/uiwidget.cpp index 1c7daf5f1..6f08fed0e 100644 --- a/src/eepp/ui/uiwidget.cpp +++ b/src/eepp/ui/uiwidget.cpp @@ -415,6 +415,7 @@ UITooltip* UIWidget::createTooltip() { mTooltip->setTooltipOf( this ); if ( !mTooltipText.empty() ) mTooltip->setText( mTooltipText ); + sendCommonEvent( Event::OnTooltipCreated ); return mTooltip; } diff --git a/src/tests/unit_tests/gitconflict_tests.cpp b/src/tests/unit_tests/gitconflict_tests.cpp index 97daf0121..eb6f3d3c6 100644 --- a/src/tests/unit_tests/gitconflict_tests.cpp +++ b/src/tests/unit_tests/gitconflict_tests.cpp @@ -135,6 +135,14 @@ UTEST( GitHistory, PaginatesFirstParentWithoutDuplicates ) { ASSERT_TRUE( third.success() ); ASSERT_EQ( 1u, third.commits.size() ); EXPECT_FALSE( third.hasMore ); + + ASSERT_EQ( EXIT_SUCCESS, run( { "branch", "older", "HEAD~2" } ) ); + Git::HistoryQuery refQuery; + refQuery.revision = "refs/heads/older"; + auto selectedRef = git.history( refQuery, temp.path.string() ); + ASSERT_TRUE( selectedRef.success() ); + ASSERT_EQ( 3u, selectedRef.commits.size() ); + EXPECT_STREQ( "commit 2", selectedRef.commits.front().subject.c_str() ); } UTEST( GitHistory, PropagatedExclusionsHideAlreadyRepresentedMainline ) { @@ -205,12 +213,14 @@ UTEST( GitHistory, HandlesEmptyRepositoryUnicodeAndHardLimit ) { ASSERT_EQ( EXIT_SUCCESS, run( { "config", "user.name", "Tést 🚀" } ) ); ASSERT_EQ( EXIT_SUCCESS, run( { "config", "user.email", "unicode@example.invalid" } ) ); const std::string subject = "unicode 🚀 | quote \" and tab\tend"; - ASSERT_EQ( EXIT_SUCCESS, run( { "commit", "--allow-empty", "-m", subject } ) ); + const std::string message = subject + "\ncontinuation on a physical second line"; + ASSERT_EQ( EXIT_SUCCESS, run( { "commit", "--allow-empty", "-m", message } ) ); query.limit = 200; auto page = git.history( query, temp.path.string() ); ASSERT_TRUE( page.success() ); ASSERT_EQ( 1u, page.commits.size() ); EXPECT_STREQ( subject.c_str(), page.commits.front().subject.c_str() ); + EXPECT_STREQ( message.c_str(), page.commits.front().message.c_str() ); EXPECT_STREQ( "Tést 🚀", page.commits.front().authorName.c_str() ); } diff --git a/src/tools/ecode/ecode.cpp b/src/tools/ecode/ecode.cpp index ed7f21bd4..668e95453 100644 --- a/src/tools/ecode/ecode.cpp +++ b/src/tools/ecode/ecode.cpp @@ -340,7 +340,8 @@ void App::setAppTitle( const std::string& title ) { if ( Engine::isMainThread() ) { mWindow->setTitle( fullTitle ); } else { - mUISceneNode->runOnMainThread( [this, fullTitle] { mWindow->setTitle( fullTitle ); } ); + mLifetime.weakHandle().run( + [fullTitle]( App* app ) { app->mWindow->setTitle( fullTitle ); } ); } } } @@ -729,8 +730,9 @@ void App::initPluginManager() { } else { // If plugin loads asynchronously and is not ready, delay the plugin enabled callback plugin->addOnReadyCallback( [this]( UICodeEditorPlugin* plugin, const Uint32& cbId ) { - mUISceneNode->runOnMainThread( - [this, plugin]() { onPluginEnabled( static_cast( plugin ) ); } ); + mLifetime.weakHandle().run( [plugin]( App* app ) { + app->onPluginEnabled( static_cast( plugin ) ); + } ); plugin->removeReadyCallback( cbId ); } ); } @@ -1041,6 +1043,7 @@ void App::onTextDropped( String text ) { App::App( const size_t& jobs, const std::vector& args ) : mArgs( args ), + mLifetime( this, nullptr ), mThreadPool( ThreadPool::createShared( jobs > 0 ? jobs : eemax( 4, Sys::getCPUCount() ) ) ), mDateTimeController( std::make_unique( this ) ), @@ -1051,6 +1054,7 @@ App::App( const size_t& jobs, const std::vector& args ) : } App::~App() { + mLifetime.invalidate(); appInstance = nullptr; mDestroyingApp = true; @@ -3588,10 +3592,10 @@ void App::loadDirTree( const std::string& path ) { Log::info( "DirTree read in: %s. Found %ld files.", clock.getElapsedTime().toString(), dirTree.getFilesCount() ); mDirTreeReady = true; - mUISceneNode->runOnMainThread( [this] { - mUniversalLocator->updateFilesTable(); - if ( mSplitter->curEditorExistsAndFocused() ) - syncProjectTreeWithEditor( mSplitter->getCurEditor() ); + mLifetime.weakHandle().run( []( App* app ) { + app->mUniversalLocator->updateFilesTable(); + if ( app->mSplitter->curEditorExistsAndFocused() ) + app->syncProjectTreeWithEditor( app->mSplitter->getCurEditor() ); } ); removeFolderWatches(); if ( mFileWatcher ) { @@ -3772,11 +3776,11 @@ void App::newFile( const FileInfo& file ) { errorMsgBox( i18n( "couldnt_create_file", "Couldn't create file." ) ); } else if ( mProjectTreeView ) { // We wait 100 ms to get the notification from the file system - mUISceneNode->runOnMainThread( - [this, newFilePath] { - if ( !mFileSystemModel || !mProjectTreeView ) + mLifetime.weakHandle().run( + [newFilePath]( App* app ) { + if ( !app->mFileSystemModel || !app->mProjectTreeView ) return; - loadFileFromPathOrFocus( newFilePath ); + app->loadFileFromPathOrFocus( newFilePath ); }, Milliseconds( 100 ) ); } @@ -3799,13 +3803,14 @@ void App::newFolder( const FileInfo& file ) { errorMsgBox( i18n( "couldnt_create_directory", "Couldn't create directory." ) ); } else if ( mProjectTreeView ) { // We wait 100 ms to get the notification from the file system - mUISceneNode->runOnMainThread( - [this, newFolderPath] { - if ( !mFileSystemModel || !mProjectTreeView ) + mLifetime.weakHandle().run( + [newFolderPath]( App* app ) { + if ( !app->mFileSystemModel || !app->mProjectTreeView ) return; std::string nfp( newFolderPath ); - FileSystem::filePathRemoveBasePath( mFileSystemModel->getRootPath(), nfp ); - mProjectTreeView->openRowWithPath( nfp ); + FileSystem::filePathRemoveBasePath( app->mFileSystemModel->getRootPath(), + nfp ); + app->mProjectTreeView->openRowWithPath( nfp ); }, Milliseconds( 100 ) ); } @@ -4929,6 +4934,7 @@ void App::init( InitParameters& params ) { eemax( mWindow->getScale(), mConfig.windowState.pixelDensity ) ); mUISceneNode = UISceneNode::New(); + mLifetime.setDispatcher( mUISceneNode ); mUISceneNode->setThreadPool( mThreadPool ); mUIColorScheme = mConfig.ui.colorScheme; diff --git a/src/tools/ecode/ecode.hpp b/src/tools/ecode/ecode.hpp index 38681e930..9aa276c07 100644 --- a/src/tools/ecode/ecode.hpp +++ b/src/tools/ecode/ecode.hpp @@ -19,6 +19,7 @@ #include "uistatusbar.hpp" #include "universallocator.hpp" #include +#include #include #include #include @@ -672,6 +673,7 @@ class App : public UICodeEditorSplitter::Client, public PluginContextProvider { std::vector mArgs; EE::Window::Window* mWindow{ nullptr }; UISceneNode* mUISceneNode{ nullptr }; + MainThreadLifetime mLifetime; UIConsole* mConsole{ nullptr }; std::string mCurWindowTitle; std::string mWindowTitle{ "ecode" }; diff --git a/src/tools/ecode/globalsearchcontroller.cpp b/src/tools/ecode/globalsearchcontroller.cpp index 76083621d..fd2c0eb0e 100644 --- a/src/tools/ecode/globalsearchcontroller.cpp +++ b/src/tools/ecode/globalsearchcontroller.cpp @@ -7,7 +7,10 @@ static int LOCATEBAR_MAX_VISIBLE_ITEMS = 18; GlobalSearchController::GlobalSearchController( UICodeEditorSplitter* editorSplitter, UISceneNode* sceneNode, App* app ) : - mSplitter( editorSplitter ), mUISceneNode( sceneNode ), mApp( app ) { + mSplitter( editorSplitter ), + mUISceneNode( sceneNode ), + mLifetime( this, sceneNode ), + mApp( app ) { mApp->getPluginManager()->subscribeMessages( "GlobalSearchController", [this]( const PluginMessage& msg ) -> PluginRequestHandle { return processMessage( msg ); @@ -831,20 +834,22 @@ void GlobalSearchController::doGlobalSearch( String text, String filter, bool ca filter]( const ProjectSearch::ConsolidatedResult& res ) { Log::info( "Global search for \"%s\" took %s", search.c_str(), clock.getElapsedTime().toString() ); - mUISceneNode->runOnMainThread( [this, res = std::move( res ), search, searchReplace, - searchAgain, escapeSequence, searchType, filter] { - mLastSearchConfig = std::move( res.first ); + mLifetime.weakHandle().run( [res = std::move( res ), search, searchReplace, searchAgain, + escapeSequence, searchType, + filter]( GlobalSearchController* controller ) mutable { + controller->mLastSearchConfig = std::move( res.first ); auto model = ProjectSearch::asModel( res.second ); model->setOpType( searchType ); - updateGlobalSearchHistory( model, search, filter, searchReplace, searchAgain, - escapeSequence ); - updateGlobalSearchBarResults( search, model, searchReplace, escapeSequence ); - auto* loader = mGlobalSearchLayout->getParent()->find( "loader" ); + controller->updateGlobalSearchHistory( model, search, filter, searchReplace, + searchAgain, escapeSequence ); + controller->updateGlobalSearchBarResults( search, model, searchReplace, + escapeSequence ); + auto* loader = controller->mGlobalSearchLayout->getParent()->find( "loader" ); if ( loader ) { loader->setVisible( false ); loader->close(); } - mCurSearch = nullptr; + controller->mCurSearch = nullptr; } ); }, caseSensitive, wholeWord, searchType, filters, mApp->getCurrentProject(), openDocs ); @@ -951,12 +956,12 @@ PluginRequestHandle GlobalSearchController::processMessage( const PluginMessage& sample.line.substr( sample.position.start().column(), sample.position.end().column() - sample.position.start().column() ); - mUISceneNode->runOnMainThread( [this, search, model] { - showGlobalSearch( false ); - mGlobalSearchInput->setText( search ); - mGlobalSearchWhereInput->setText( "" ); - updateGlobalSearchHistory( model, search, "", false, false, false ); - updateGlobalSearchBarResults( search, model, false, false ); + mLifetime.weakHandle().run( [search, model]( GlobalSearchController* controller ) { + controller->showGlobalSearch( false ); + controller->mGlobalSearchInput->setText( search ); + controller->mGlobalSearchWhereInput->setText( "" ); + controller->updateGlobalSearchHistory( model, search, "", false, false, false ); + controller->updateGlobalSearchBarResults( search, model, false, false ); } ); return {}; diff --git a/src/tools/ecode/globalsearchcontroller.hpp b/src/tools/ecode/globalsearchcontroller.hpp index 7ea91bc03..39e24bff4 100644 --- a/src/tools/ecode/globalsearchcontroller.hpp +++ b/src/tools/ecode/globalsearchcontroller.hpp @@ -5,6 +5,7 @@ #include "plugins/pluginmanager.hpp" #include "projectsearch.hpp" #include +#include namespace ecode { @@ -68,6 +69,7 @@ class GlobalSearchController { protected: UICodeEditorSplitter* mSplitter{ nullptr }; UISceneNode* mUISceneNode{ nullptr }; + MainThreadLifetime mLifetime; App* mApp{ nullptr }; UIGlobalSearchBar* mGlobalSearchBarLayout{ nullptr }; diff --git a/src/tools/ecode/plugins/aiassistant/chatui.cpp b/src/tools/ecode/plugins/aiassistant/chatui.cpp index 69f29d2dd..8948e8bd8 100644 --- a/src/tools/ecode/plugins/aiassistant/chatui.cpp +++ b/src/tools/ecode/plugins/aiassistant/chatui.cpp @@ -608,6 +608,7 @@ LLMChatUI::LLMChatUI( PluginManager* manager ) : UILinearLayout(), WidgetCommandExecuter( getInput() ), mManager( manager ), + mLifetime( this, getUISceneNode() ), mDisplayReasoning( getPlugin() && getPlugin()->displayReasoning() ) { setClass( "llm_chatui" ); setLayoutSizePolicy( SizePolicy::MatchParent, SizePolicy::MatchParent ); @@ -1171,6 +1172,7 @@ LLMChatUI::LLMChatUI( PluginManager* manager ) : } LLMChatUI::~LLMChatUI() { + mLifetime.invalidate(); if ( mRequest ) { mRequest->cancelCb = nullptr; mRequest->doneCb = nullptr; @@ -3343,11 +3345,12 @@ void LLMChatUI::showAttachFile() { ctx->getDirTree()->asyncMatchTree( ProjectDirectoryTree::MatchType::Fuzzy, text, 100, [this, text]( auto res ) { - mUISceneNode->runOnMainThread( [this, res] { - mLocateTable->setModel( res ); - mLocateTable->getSelection().set( mLocateTable->getModel()->index( 0 ) ); - mLocateTable->scrollToTop(); - updateLocateBarColumns(); + mLifetime.weakHandle().run( [res]( LLMChatUI* chat ) { + chat->mLocateTable->setModel( res ); + chat->mLocateTable->getSelection().set( + chat->mLocateTable->getModel()->index( 0 ) ); + chat->mLocateTable->scrollToTop(); + chat->updateLocateBarColumns(); } ); }, ctx->getCurrentProject() ); diff --git a/src/tools/ecode/plugins/aiassistant/chatui.hpp b/src/tools/ecode/plugins/aiassistant/chatui.hpp index d05b222de..8fb719578 100644 --- a/src/tools/ecode/plugins/aiassistant/chatui.hpp +++ b/src/tools/ecode/plugins/aiassistant/chatui.hpp @@ -5,6 +5,7 @@ #include "llmchatcompletionrequest.hpp" #include "protocol.hpp" +#include #include #include @@ -102,6 +103,7 @@ class LLMChatUI : public UILinearLayout, public WidgetCommandExecuter { std::string mSummary; long mTimestamp{ 0 }; PluginManager* mManager{ nullptr }; + MainThreadLifetime mLifetime; UISplitter* mChatSplitter{ nullptr }; UIWidget* mChatsList{ nullptr }; UICodeEditor* mChatInput{ nullptr }; diff --git a/src/tools/ecode/plugins/debugger/debuggerclientlistener.cpp b/src/tools/ecode/plugins/debugger/debuggerclientlistener.cpp index f9d7faf1d..7df98a796 100644 --- a/src/tools/ecode/plugins/debugger/debuggerclientlistener.cpp +++ b/src/tools/ecode/plugins/debugger/debuggerclientlistener.cpp @@ -189,7 +189,7 @@ void DebuggerClientListener::initUI() { void DebuggerClientListener::stateChanged( DebuggerClient::State state, const SessionId& ) { if ( state == DebuggerClient::State::Initializing ) { - mPlugin->getManager()->getUISceneNode()->runOnMainThread( [this] { initUI(); } ); + mPlugin->mLifetime.weakHandle().run( [this]( DebuggerPlugin* ) { initUI(); } ); } } @@ -345,9 +345,9 @@ void DebuggerClientListener::outputProduced( const Output& output ) { auto buffer = output.output; auto sdc = getStatusDebuggerController(); if ( sdc == nullptr || sdc->getUIConsole() == nullptr ) { - mPlugin->getUISceneNode()->runOnMainThread( - [this, buffer = std::move( buffer )]() mutable { - mPlugin->initStatusDebuggerController(); + mPlugin->mLifetime.weakHandle().run( + [this, buffer = std::move( buffer )]( DebuggerPlugin* plugin ) mutable { + plugin->initStatusDebuggerController(); auto sdc = getStatusDebuggerController(); sdc->insertConsoleBuffer( std::move( buffer ) ); } ); @@ -407,8 +407,9 @@ void DebuggerClientListener::changeScope( const StackFrame& f ) { TextRange range{ { f.line - 1, f.column - 1 }, { f.line - 1, f.column - 1 } }; std::string path( f.source->path ); - mPlugin->getUISceneNode()->runOnMainThread( - [this, path, range] { mPlugin->getPluginContext()->focusOrLoadFile( path, range ); } ); + mPlugin->mLifetime.weakHandle().run( [path, range]( DebuggerPlugin* plugin ) { + plugin->getPluginContext()->focusOrLoadFile( path, range ); + } ); auto sdc = getStatusDebuggerController(); if ( sdc && sdc->getUIStack() ) diff --git a/src/tools/ecode/plugins/debugger/debuggerplugin.cpp b/src/tools/ecode/plugins/debugger/debuggerplugin.cpp index 41ecf2736..e333309d0 100644 --- a/src/tools/ecode/plugins/debugger/debuggerplugin.cpp +++ b/src/tools/ecode/plugins/debugger/debuggerplugin.cpp @@ -155,7 +155,7 @@ Plugin* DebuggerPlugin::NewSync( PluginManager* pluginManager ) { } DebuggerPlugin::DebuggerPlugin( PluginManager* pluginManager, bool sync ) : - PluginBase( pluginManager ) { + PluginBase( pluginManager ), mLifetime( this, getUISceneNode() ) { if ( sync ) { load( pluginManager ); } else { @@ -164,6 +164,7 @@ DebuggerPlugin::DebuggerPlugin( PluginManager* pluginManager, bool sync ) : } DebuggerPlugin::~DebuggerPlugin() { + mLifetime.invalidate(); waitUntilLoaded(); mShuttingDown = true; @@ -712,6 +713,7 @@ void DebuggerPlugin::loadProjectConfiguration( const std::string& path ) { } void DebuggerPlugin::loadProjectConfigurations() { + const auto lifetime = mLifetime.weakHandle(); if ( mProjectPath.empty() ) return; @@ -720,7 +722,7 @@ void DebuggerPlugin::loadProjectConfigurations() { mDapConfigs.clear(); } - mThreadPool->run( [this] { + mThreadPool->run( [this, lifetime] { std::string config; if ( mLoadVSCodeLaunchConfig ) { config = mProjectPath + ".vscode/launch.json"; @@ -731,9 +733,9 @@ void DebuggerPlugin::loadProjectConfigurations() { if ( FileSystem::fileExists( config ) ) loadProjectConfiguration( config ); - getUISceneNode()->runOnMainThread( [this] { - updateDebuggerConfigurationList(); - updateSelectedDebugConfig(); + lifetime.run( []( DebuggerPlugin* plugin ) { + plugin->updateDebuggerConfigurationList(); + plugin->updateSelectedDebugConfig(); } ); } ); } @@ -744,11 +746,11 @@ PluginRequestHandle DebuggerPlugin::processMessage( const PluginMessage& msg ) { mProjectPath = msg.asJSON()["folder"]; if ( getUISceneNode() && mSidePanel ) { - getUISceneNode()->runOnMainThread( [this] { - if ( mProjectPath.empty() ) { - hideSidePanel(); - Lock l( mDapsMutex ); - mDapConfigs.clear(); + mLifetime.weakHandle().run( []( DebuggerPlugin* plugin ) { + if ( plugin->mProjectPath.empty() ) { + plugin->hideSidePanel(); + Lock l( plugin->mDapsMutex ); + plugin->mDapConfigs.clear(); } } ); } @@ -760,6 +762,7 @@ PluginRequestHandle DebuggerPlugin::processMessage( const PluginMessage& msg ) { break; } case ecode::PluginMessageType::UIReady: { + mLifetime.setDispatcher( getUISceneNode() ); registerCommands( getPluginContext()->getMainLayout() ); for ( const auto& kb : mKeyBindings ) { getPluginContext()->getMainLayout()->getKeyBindings().addKeybindString( kb.second, @@ -784,9 +787,9 @@ void DebuggerPlugin::updateUI() { if ( !getUISceneNode() ) return; - getUISceneNode()->runOnMainThread( [this] { - buildSidePanelTab(); - buildStatusBar(); + mLifetime.weakHandle().run( []( DebuggerPlugin* plugin ) { + plugin->buildSidePanelTab(); + plugin->buildStatusBar(); } ); } @@ -1495,15 +1498,16 @@ void DebuggerPlugin::registerCommands( TCommandRegister* executer ) { if ( exitCode == 0 ) { runCurrentConfig(); } else { - getPluginContext()->getUISceneNode()->runOnMainThread( [this] { + mLifetime.weakHandle().run( []( DebuggerPlugin* plugin ) { auto msgBox = UIMessageBox::New( UIMessageBox::YES_NO, - i18n( "build_failed_debug_anyways", - "Building the project failed, do you want to " - "debug the binary anyways?" ) ); - msgBox->setTitle( i18n( "build_failed", "Build Failed" ) ); + plugin->i18n( "build_failed_debug_anyways", + "Building the project failed, do you want to " + "debug the binary anyways?" ) ); + msgBox->setTitle( plugin->i18n( "build_failed", "Build Failed" ) ); msgBox->setCloseShortcut( { KEY_ESCAPE, KEYMOD_NONE } ); - msgBox->on( Event::OnConfirm, [this]( auto ) { runCurrentConfig(); } ); + msgBox->on( Event::OnConfirm, + [plugin]( auto ) { plugin->runCurrentConfig(); } ); msgBox->showWhenReady(); } ); } @@ -2348,11 +2352,12 @@ void DebuggerPlugin::run( const std::string& debugger, ProtocolSettings&& protoc std::function doneFn ) { if ( !FileSystem::fileExists( cmd ) ) cmd = FileSystem::fileNameFromPath( cmd ); - getUISceneNode()->runOnMainThread( [this, isIntegrated, cmd = std::move( cmd ), cwd, args, - doneFn = std::move( doneFn ), env = std::move( env )] { + mLifetime.weakHandle().run( [isIntegrated, cmd = std::move( cmd ), cwd, args, + doneFn = std::move( doneFn ), + env = std::move( env )]( DebuggerPlugin* plugin ) { if ( isIntegrated || !env.empty() ) { UITerminal* term = - getPluginContext()->getTerminalManager()->createTerminalInSplitter( + plugin->getPluginContext()->getTerminalManager()->createTerminalInSplitter( cwd, cmd, args, env, false, false ); doneFn( term && term->getTerm() && term->getTerm()->getTerminal() && @@ -2361,15 +2366,16 @@ void DebuggerPlugin::run( const std::string& debugger, ProtocolSettings&& protoc : 0 ); } else { std::string fcmd = cmd + ( !args.empty() ? " " : "" ) + String::join( args, ' ' ); - doneFn( - getPluginContext()->getTerminalManager()->openInExternalTerminal( fcmd, cwd ) ); + doneFn( plugin->getPluginContext()->getTerminalManager()->openInExternalTerminal( + fcmd, cwd ) ); } } ); }; dap->runTargetCb = [this] { - getUISceneNode()->runOnMainThread( - [this] { getPluginContext()->runCommand( "project-run-executable" ); } ); + mLifetime.weakHandle().run( []( DebuggerPlugin* plugin ) { + plugin->getPluginContext()->runCommand( "project-run-executable" ); + } ); }; mDebugger->start(); diff --git a/src/tools/ecode/plugins/debugger/debuggerplugin.hpp b/src/tools/ecode/plugins/debugger/debuggerplugin.hpp index 0110c2e2c..1080b19c5 100644 --- a/src/tools/ecode/plugins/debugger/debuggerplugin.hpp +++ b/src/tools/ecode/plugins/debugger/debuggerplugin.hpp @@ -6,6 +6,7 @@ #include "config.hpp" #include "debuggerclientlistener.hpp" #include "models/breakpointsmodel.hpp" +#include using namespace EE::UI::Models; using namespace EE::UI; @@ -90,6 +91,7 @@ class DebuggerPlugin : public PluginBase { protected: friend class DebuggerClientListener; + MainThreadLifetime mLifetime; bool mInitialized{ false }; bool mFetchRegisters{ false }; diff --git a/src/tools/ecode/plugins/debugger/statusdebuggercontroller.cpp b/src/tools/ecode/plugins/debugger/statusdebuggercontroller.cpp index f0f2142a7..5817ff8e1 100644 --- a/src/tools/ecode/plugins/debugger/statusdebuggercontroller.cpp +++ b/src/tools/ecode/plugins/debugger/statusdebuggercontroller.cpp @@ -141,11 +141,12 @@ StatusDebuggerController::getLocalDefaultKeybindings() { StatusDebuggerController::StatusDebuggerController( UISplitter* mainSplitter, UISceneNode* uiSceneNode, PluginContextProvider* pluginContext ) : - StatusBarElement( mainSplitter, uiSceneNode, pluginContext ) { + StatusBarElement( mainSplitter, uiSceneNode, pluginContext ), mLifetime( this, uiSceneNode ) { mSerializedLayout = mContext->getConfig().iniState.getValue( "debugger", "panel_layout" ); } StatusDebuggerController::~StatusDebuggerController() { + mLifetime.invalidate(); mEventConnections.clear(); mTabWidgetEventConnections.clear(); if ( mContainer ) @@ -232,20 +233,20 @@ void StatusDebuggerController::setDebuggingState( State state ) { if ( !mContainer ) return; - mUISceneNode->runOnMainThread( [this, state] { - mUIButStart->setVisible( state == State::NotStarted ) + mLifetime.weakHandle().run( [state]( StatusDebuggerController* controller ) { + controller->mUIButStart->setVisible( state == State::NotStarted ) ->setEnabled( state == State::NotStarted ); - mUIButStop->setVisible( state != State::NotStarted ) + controller->mUIButStop->setVisible( state != State::NotStarted ) ->setEnabled( state != State::NotStarted ); - mUIButContinue->setVisible( state != State::NotStarted ) + controller->mUIButContinue->setVisible( state != State::NotStarted ) ->setEnabled( state == State::Paused ); - mUIButPause->setVisible( state != State::NotStarted ) + controller->mUIButPause->setVisible( state != State::NotStarted ) ->setEnabled( state == State::Running ); - mUIButStepOver->setVisible( state != State::NotStarted ) + controller->mUIButStepOver->setVisible( state != State::NotStarted ) ->setEnabled( state == State::Paused ); - mUIButStepInto->setVisible( state != State::NotStarted ) + controller->mUIButStepInto->setVisible( state != State::NotStarted ) ->setEnabled( state == State::Paused ); - mUIButStepOut->setVisible( state != State::NotStarted ) + controller->mUIButStepOut->setVisible( state != State::NotStarted ) ->setEnabled( state == State::Paused ); } ); } diff --git a/src/tools/ecode/plugins/debugger/statusdebuggercontroller.hpp b/src/tools/ecode/plugins/debugger/statusdebuggercontroller.hpp index 3b6f6fcce..8d2756658 100644 --- a/src/tools/ecode/plugins/debugger/statusdebuggercontroller.hpp +++ b/src/tools/ecode/plugins/debugger/statusdebuggercontroller.hpp @@ -5,6 +5,7 @@ #include "models/breakpointsmodel.hpp" #include #include +#include #include #include #include @@ -113,6 +114,7 @@ class StatusDebuggerController : public StatusBarElement, public UITabWidgetSpli bool mRestoringLayout{ false }; bool mRightPanelDropPreview{ false }; std::string mSerializedLayout; + MainThreadLifetime mLifetime; void createContainer(); diff --git a/src/tools/ecode/plugins/git/git.cpp b/src/tools/ecode/plugins/git/git.cpp index 07be4aa6b..5211369da 100644 --- a/src/tools/ecode/plugins/git/git.cpp +++ b/src/tools/ecode/plugins/git/git.cpp @@ -37,7 +37,7 @@ Git::HistoryPage Git::history( const HistoryQuery& query, const std::string& pro "--first-parent", String::format( "--max-count=%zu", query.limit + 1 ), "-z", - "--format=%H%x00%h%x00%P%x00%an%x00%ae%x00%at%x00%ct%x00%s", + "--format=%H%x00%h%x00%P%x00%an%x00%ae%x00%at%x00%ct%x00%B", query.continuation.empty() ? query.revision : query.continuation }; if ( !query.exclusions.empty() ) { @@ -86,7 +86,12 @@ Git::HistoryPage Git::history( const HistoryQuery& query, const std::string& pro } commit.authorName = fields[3]; commit.authorEmail = fields[4]; - commit.subject = fields[7]; + commit.message = fields[7]; + while ( !commit.message.empty() && + ( commit.message.back() == '\n' || commit.message.back() == '\r' ) ) + commit.message.pop_back(); + const size_t subjectEnd = commit.message.find_first_of( "\r\n" ); + commit.subject = commit.message.substr( 0, subjectEnd ); if ( !parseHistoryTimestamp( fields[5], commit.authorTime ) || !parseHistoryTimestamp( fields[6], commit.commitTime ) ) { page.returnCode = EXIT_FAILURE; diff --git a/src/tools/ecode/plugins/git/git.hpp b/src/tools/ecode/plugins/git/git.hpp index a67d723c7..fcde7edd9 100644 --- a/src/tools/ecode/plugins/git/git.hpp +++ b/src/tools/ecode/plugins/git/git.hpp @@ -206,6 +206,7 @@ class Git { std::string shortHash; std::vector parents; std::string subject; + std::string message; std::string authorName; std::string authorEmail; int64_t authorTime{ 0 }; diff --git a/src/tools/ecode/plugins/git/gitbranchmodel.cpp b/src/tools/ecode/plugins/git/gitbranchmodel.cpp index 77fbd08b5..2e9e945bf 100644 --- a/src/tools/ecode/plugins/git/gitbranchmodel.cpp +++ b/src/tools/ecode/plugins/git/gitbranchmodel.cpp @@ -37,8 +37,27 @@ GitBranchModel::GitBranchModel( std::vector&& branches, size_t hash type.emplace_back( std::move( branch ) ); } for ( auto& branch : branchTypes ) { - mBranches.emplace_back( - BranchData{ std::move( branch.first ), std::move( branch.second ) } ); + BranchData data{ std::move( branch.first ), std::move( branch.second ), {} }; + data.historyDisplay.reserve( data.data.size() ); + for ( const auto& ref : data.data ) { + String kind; + switch ( ref.type ) { + case Git::RefType::Head: + kind = mPlugin->i18n( "git_local_branch", "Local" ); + break; + case Git::RefType::Remote: + kind = mPlugin->i18n( "git_remote_branch", "Remote" ); + break; + case Git::RefType::Tag: + kind = mPlugin->i18n( "git_tag", "Tag" ); + break; + default: + break; + } + data.historyDisplay.emplace_back( kind + String{ " · " } + + String::fromUtf8( ref.name ) ); + } + mBranches.emplace_back( std::move( data ) ); } } @@ -119,6 +138,8 @@ Variant GitBranchModel::data( const ModelIndex& index, ModelRole role ) const { return Variant( branch.typeStr() ); case Column::LastCommit: return Variant( branch.lastCommit.c_str() ); + case Column::HistoryDisplay: + return Variant( &mBranches[index.internalId()].historyDisplay[index.row()] ); } return Variant( GIT_EMPTY ); } @@ -166,6 +187,10 @@ Variant GitBranchModel::data( const ModelIndex& index, ModelRole role ) const { } Git::Branch GitBranchModel::branch( const ModelIndex& index ) const { + return branchRef( index ); +} + +const Git::Branch& GitBranchModel::branchRef( const ModelIndex& index ) const { return *static_cast( index.internalData() ); } diff --git a/src/tools/ecode/plugins/git/gitbranchmodel.hpp b/src/tools/ecode/plugins/git/gitbranchmodel.hpp index 9e1375e6e..4af61b975 100644 --- a/src/tools/ecode/plugins/git/gitbranchmodel.hpp +++ b/src/tools/ecode/plugins/git/gitbranchmodel.hpp @@ -25,11 +25,12 @@ class GitBranchModel : public Model { static size_t hashBranches( const std::vector& branches ); - enum Column { Name, Remote, Type, LastCommit }; + enum Column { Name, Remote, Type, LastCommit, HistoryDisplay }; struct BranchData { std::string branch; std::vector data; + std::vector historyDisplay; }; std::string refTypeToString( Git::RefType type ) const; @@ -40,7 +41,7 @@ class GitBranchModel : public Model { size_t rowCount( const ModelIndex& index ) const; - size_t columnCount( const ModelIndex& ) const { return 4; } + size_t columnCount( const ModelIndex& ) const { return 5; } ModelIndex parentIndex( const ModelIndex& index ) const; @@ -58,6 +59,8 @@ class GitBranchModel : public Model { Git::Branch branch( const ModelIndex& index ) const; + const Git::Branch& branchRef( const ModelIndex& index ) const; + std::optional branch( const std::string& name ) const; ModelIndex refTypeIndex( Git::RefType refType ) const; diff --git a/src/tools/ecode/plugins/git/githistorymodel.cpp b/src/tools/ecode/plugins/git/githistorymodel.cpp index fe833a499..3a7c1dfe2 100644 --- a/src/tools/ecode/plugins/git/githistorymodel.cpp +++ b/src/tools/ecode/plugins/git/githistorymodel.cpp @@ -111,7 +111,7 @@ GitHistoryModel::commitNode( Git::Commit commit, Node* parent, item->date = Sys::epochToString( item->commit.commitTime ); item->hash = String::fromUtf8( item->commit.shortHash ); item->tooltip = String::format( "%s\n%s <%s>\n%s", item->commit.hash, item->commit.authorName, - item->commit.authorEmail, item->commit.subject ); + item->commit.authorEmail, item->commit.message ); item->message = item->date + String{ " · " } + item->author + String{ " · " } + item->hash; if ( item->commit.parents.size() == 2 ) { item->query = query; @@ -207,6 +207,14 @@ void GitHistoryModel::setChildrenPage( Node* item, Git::HistoryPage page, return; Nodes children; fillPage( children, item, std::move( page ), query ); + if ( children.empty() ) { + auto child = std::make_unique(); + child->type = NodeType::Empty; + child->parent = item; + child->message = + mPlugin->i18n( "git_history_no_additional_commits", "No additional commits" ); + children.emplace_back( std::move( child ) ); + } item->childrenLoading = false; item->childrenLoaded = true; replaceChildren( item, std::move( children ) ); diff --git a/src/tools/ecode/plugins/git/githistorytreeview.cpp b/src/tools/ecode/plugins/git/githistorytreeview.cpp index 7cff1d23f..ace54c6d5 100644 --- a/src/tools/ecode/plugins/git/githistorytreeview.cpp +++ b/src/tools/ecode/plugins/git/githistorytreeview.cpp @@ -1,6 +1,7 @@ #include "githistorytreeview.hpp" #include "githistorymodel.hpp" #include +#include namespace ecode { @@ -9,6 +10,12 @@ GitHistoryTreeViewCell::GitHistoryTreeViewCell() : mHintColor( Color::fromString( mUISceneNode->getRoot()->getUIStyle()->getVariable( "--font-hint" ).getValue() ) ) { mTextBox->setTextOverflow( "ellipsis" ); + on( Event::OnTooltipCreated, []( const Event* event ) { + auto* tooltip = event->getNode()->asType()->getTooltip(); + tooltip->setMaxWidthEq( "60%" ); + tooltip->setWordWrap( true ); + tooltip->setHorizontalAlign( UI_HALIGN_LEFT ); + } ); } Sizef GitHistoryTreeViewCell::updateLayout() { @@ -58,4 +65,13 @@ UIWidget* GitHistoryTreeView::createCell( UIWidget* rowWidget, const ModelIndex& return setupCell( widget, rowWidget, index ); } +UIWidget* GitHistoryTreeView::updateCell( const Vector2& posIndex, const ModelIndex& index, + const size_t& indentLevel, const Float& yOffset ) { + UIWidget* widget = UITreeView::updateCell( posIndex, index, indentLevel, yOffset ); + if ( index.isValid() && index.column() == static_cast( getModel()->treeColumn() ) && + getExpandersAsIcons() && getModel()->hasChildren( index ) ) + static_cast( widget )->updateCell( getModel() ); + return widget; +} + } // namespace ecode diff --git a/src/tools/ecode/plugins/git/githistorytreeview.hpp b/src/tools/ecode/plugins/git/githistorytreeview.hpp index a891220fd..34cfc382d 100644 --- a/src/tools/ecode/plugins/git/githistorytreeview.hpp +++ b/src/tools/ecode/plugins/git/githistorytreeview.hpp @@ -33,6 +33,9 @@ class GitHistoryTreeView : public UITreeView { protected: GitHistoryTreeView() = default; + + UIWidget* updateCell( const Vector2& posIndex, const ModelIndex& index, + const size_t& indentLevel, const Float& yOffset ) override; }; } // namespace ecode diff --git a/src/tools/ecode/plugins/git/gitplugin.cpp b/src/tools/ecode/plugins/git/gitplugin.cpp index 653ee871c..a48fd6f4f 100644 --- a/src/tools/ecode/plugins/git/gitplugin.cpp +++ b/src/tools/ecode/plugins/git/gitplugin.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -39,6 +40,73 @@ using json = nlohmann::json; namespace ecode { +GitHistoryRefModel::GitHistoryRefModel( std::shared_ptr source, String headLabel ) : + mSource( std::move( source ) ), mHeadLabel( std::move( headLabel ) ) { + if ( !mSource ) + return; + for ( size_t groupRow = 0; groupRow < mSource->rowCount( {} ); ++groupRow ) { + const ModelIndex group = mSource->index( groupRow, GitBranchModel::Name, {} ); + for ( size_t row = 0; row < mSource->rowCount( group ); ++row ) { + const ModelIndex index = mSource->index( row, GitBranchModel::HistoryDisplay, group ); + if ( mSource->branchRef( index ).type != Git::RefType::Stash ) + mSourceIndexes.emplace_back( index ); + } + } +} + +ModelIndex GitHistoryRefModel::index( int row, int column, const ModelIndex& parent ) const { + if ( parent.isValid() || row < 0 || static_cast( row ) >= rowCount() || column != 0 ) + return {}; + return createIndex( row, column ); +} + +Variant GitHistoryRefModel::data( const ModelIndex& index, ModelRole role ) const { + if ( role != ModelRole::Display || !index.isValid() || index.row() < 0 || + static_cast( index.row() ) >= rowCount() ) + return {}; + if ( index.row() == 0 ) + return Variant( &mHeadLabel ); + return mSource->data( mSourceIndexes[index.row() - 1], role ); +} + +std::string GitHistoryRefModel::revision( size_t index ) const { + if ( index == 0 || index > mSourceIndexes.size() ) + return "HEAD"; + const auto& branch = mSource->branchRef( mSourceIndexes[index - 1] ); + switch ( branch.type ) { + case Git::RefType::Head: + return "refs/heads/" + branch.name; + case Git::RefType::Remote: + return "refs/remotes/" + branch.name; + case Git::RefType::Tag: + return "refs/tags/" + branch.name; + default: + return "HEAD"; + } +} + +bool GitHistoryRefModel::isRevision( size_t index, std::string_view revision ) const { + if ( index == 0 || index > mSourceIndexes.size() ) + return revision == "HEAD"; + const auto& branch = mSource->branchRef( mSourceIndexes[index - 1] ); + std::string_view prefix; + switch ( branch.type ) { + case Git::RefType::Head: + prefix = "refs/heads/"; + break; + case Git::RefType::Remote: + prefix = "refs/remotes/"; + break; + case Git::RefType::Tag: + prefix = "refs/tags/"; + break; + default: + return false; + } + return revision.size() == prefix.size() + branch.name.size() && + revision.starts_with( prefix ) && revision.substr( prefix.size() ) == branch.name; +} + void GitPlugin::registerSettings( SettingsPage& page ) { page.addGroup( i18n( "general", "General" ) ); page.addText( "ui-refresh-frequency", "/config/ui_refresh_frequency", @@ -376,10 +444,8 @@ void GitPlugin::endModelStyler() { void GitPlugin::updateUINow( bool force ) { if ( !mGit || !getUISceneNode() ) return; - mLifetime.setDispatcher( getUISceneNode() ); - if ( !mProjectPath.empty() ) - getUISceneNode()->runOnMainThread( [this] { buildSidePanelTab(); } ); + mLifetime.weakHandle().run( []( GitPlugin* plugin ) { plugin->buildSidePanelTab(); } ); updateStatus( force ); updateBranches(); @@ -389,7 +455,10 @@ void GitPlugin::updateUI() { if ( !mGit || !getUISceneNode() ) return; - getUISceneNode()->debounce( [this] { updateUINow(); }, mRefreshFreq, GIT_STATUS_UPDATE_TAG ); + const auto lifetime = mLifetime.weakHandle(); + getUISceneNode()->debounce( + [lifetime] { lifetime.run( []( GitPlugin* plugin ) { plugin->updateUINow(); } ); }, + mRefreshFreq, GIT_STATUS_UPDATE_TAG ); } void GitPlugin::updateStatusBarSync() { @@ -658,8 +727,6 @@ void GitPlugin::updateStatus( bool force ) { } PluginRequestHandle GitPlugin::processMessage( const PluginMessage& msg ) { - if ( getUISceneNode() ) - mLifetime.setDispatcher( getUISceneNode() ); switch ( msg.type ) { case PluginMessageType::WorkspaceFolderChanged: { if ( mGit ) { @@ -701,9 +768,9 @@ PluginRequestHandle GitPlugin::processMessage( const PluginMessage& msg ) { } if ( getUISceneNode() && mSidePanel ) { - getUISceneNode()->runOnMainThread( [this] { - if ( mProjectPath.empty() ) { - hideSidePanel(); + mLifetime.weakHandle().run( []( GitPlugin* plugin ) { + if ( plugin->mProjectPath.empty() ) { + plugin->hideSidePanel(); } } ); } @@ -717,6 +784,7 @@ PluginRequestHandle GitPlugin::processMessage( const PluginMessage& msg ) { break; } case ecode::PluginMessageType::UIReady: { + mLifetime.setDispatcher( getUISceneNode() ); if ( !mInitialized ) updateUINow(); if ( mModelStylerId == 0 ) @@ -895,32 +963,34 @@ void GitPlugin::checkout( Git::Branch branch ) { if ( !mGit ) return; - const auto checkOutFn = [this, branch]( bool createLocal ) { + const auto lifetime = mLifetime.weakHandle(); + const auto git = mGit; + const std::string repo = repoSelected(); + const auto checkOutFn = [this, branch, lifetime, git, repo]( bool createLocal ) { mLoader->setVisible( true ); - mThreadPool->run( [this, branch, createLocal] { - auto result = - createLocal ? mGit->checkoutAndCreateLocalBranch( branch.name, "", repoSelected() ) - : mGit->checkout( branch.name, repoSelected() ); - if ( result.success() ) { - { - std::string repoSel = repoSelected(); - Lock l( mGitBranchMutex ); - mGitBranches[repoSel] = branch.name; - } - if ( mBranchesTree->getModel() ) { - if ( createLocal ) - updateBranches(); - else - mBranchesTree->getModel()->invalidate( Model::DontInvalidateIndexes ); - } - } else { - showMessage( LSPMessageType::Warning, result.result ); - } - getUISceneNode()->runOnMainThread( [this, success = result.success()] { - if ( success ) - invalidateHistory(); - mLoader->setVisible( false ); - } ); + mThreadPool->run( [branch, createLocal, lifetime, git, repo] { + auto result = createLocal ? git->checkoutAndCreateLocalBranch( branch.name, "", repo ) + : git->checkout( branch.name, repo ); + lifetime.run( + [branch, createLocal, repo, result = std::move( result )]( GitPlugin* plugin ) { + if ( result.success() ) { + { + Lock l( plugin->mGitBranchMutex ); + plugin->mGitBranches[repo] = branch.name; + } + if ( plugin->mBranchesTree->getModel() ) { + if ( createLocal ) + plugin->updateBranches(); + else + plugin->mBranchesTree->getModel()->invalidate( + Model::DontInvalidateIndexes ); + } + plugin->invalidateHistory(); + } else { + plugin->showMessage( LSPMessageType::Warning, result.result ); + } + plugin->mLoader->setVisible( false ); + } ); } ); }; @@ -1352,8 +1422,9 @@ void GitPlugin::discard( const std::string& file ) { } void GitPlugin::openFile( const std::string& file ) { - getUISceneNode()->runOnMainThread( [this, file] { - mManager->getLoadFileFn()( mGit->getProjectPath() + file, []( auto, auto ) {} ); + mLifetime.weakHandle().run( [file]( GitPlugin* plugin ) { + plugin->mManager->getLoadFileFn()( plugin->mGit->getProjectPath() + file, + []( auto, auto ) {} ); } ); } @@ -1956,13 +2027,13 @@ void GitPlugin::abortConflictOperation() { } void GitPlugin::diff( const Git::DiffMode mode, const std::string& repoPath ) { - mThreadPool->run( [this, mode, repoPath] { + const auto lifetime = mLifetime.weakHandle(); + mThreadPool->run( [this, mode, repoPath, lifetime] { auto res = mGit->diff( mode, repoPath ); if ( res.fail() ) return; - std::string repoName = this->repoName( repoPath ); - getUISceneNode()->runOnMainThread( [this, mode, res, repoName, repoPath] { + lifetime.run( [mode, res = std::move( res ), repoPath]( GitPlugin* plugin ) { std::string modeName; switch ( mode ) { case Git::DiffHead: { @@ -1973,7 +2044,7 @@ void GitPlugin::diff( const Git::DiffMode mode, const std::string& repoPath ) { modeName = "staged"; break; } - getPluginContext()->loadDiffFromMemory( + plugin->getPluginContext()->loadDiffFromMemory( res.result, UIDiffView::isMultiFileDiff( res.result ) ? modeName : "", "", repoPath ); } ); @@ -1981,11 +2052,12 @@ void GitPlugin::diff( const Git::DiffMode mode, const std::string& repoPath ) { } void GitPlugin::diff( const std::string& file, Git::GitStatusType status ) { - mThreadPool->run( [this, file, status] { + const auto lifetime = mLifetime.weakHandle(); + mThreadPool->run( [this, file, status, lifetime] { auto filePath = fixFilePath( file ); if ( status == Git::GitStatusType::Untracked ) { - getUISceneNode()->runOnMainThread( [this, filePath = std::move( filePath )] { - getPluginContext()->loadDiffFromPaths( "", filePath ); + lifetime.run( [filePath = std::move( filePath )]( GitPlugin* plugin ) { + plugin->getPluginContext()->loadDiffFromPaths( "", filePath ); } ); return; } @@ -2011,19 +2083,20 @@ void GitPlugin::diff( const std::string& file, Git::GitStatusType status ) { } } - getUISceneNode()->runOnMainThread( - [this, result = std::move( result ), filePath = std::move( filePath ), - oldImagePath = std::move( oldImagePath ), newImagePath = std::move( newImagePath )] { - getPluginContext()->loadDiffFromMemory( - result, newImagePath.empty() ? filePath : newImagePath, oldImagePath ); - } ); + lifetime.run( [result = std::move( result ), filePath = std::move( filePath ), + oldImagePath = std::move( oldImagePath ), + newImagePath = std::move( newImagePath )]( GitPlugin* plugin ) { + plugin->getPluginContext()->loadDiffFromMemory( + result, newImagePath.empty() ? filePath : newImagePath, oldImagePath ); + } ); } ); } void GitPlugin::diff( std::vector files ) { if ( files.empty() ) return; - mThreadPool->run( [this, files = std::move( files )] { + const auto lifetime = mLifetime.weakHandle(); + mThreadPool->run( [this, files = std::move( files ), lifetime] { std::string patch; std::string repoPath; for ( const auto& file : files ) { @@ -2042,10 +2115,10 @@ void GitPlugin::diff( std::vector files ) { if ( !patch.empty() && patch.back() != '\n' ) patch += '\n'; } - getUISceneNode()->runOnMainThread( - [this, patch = std::move( patch ), repoPath = std::move( repoPath )] { - getPluginContext()->loadDiffFromMemory( patch, "selected files", "", repoPath ); - } ); + lifetime.run( [patch = std::move( patch ), + repoPath = std::move( repoPath )]( GitPlugin* plugin ) { + plugin->getPluginContext()->loadDiffFromMemory( patch, "selected files", "", repoPath ); + } ); } ); } @@ -2253,17 +2326,55 @@ void GitPlugin::invalidateHistory() { } void GitPlugin::updateHistoryHeader() { - if ( !mHistoryRefText ) + if ( !mHistoryRefDropDown || !mHistoryRefModel ) return; - std::string branch; + auto rowCount = mHistoryRefModel->rowCount(); + for ( size_t i = 0; i < rowCount; ++i ) { + if ( mHistoryRefModel->isRevision( i, mHistoryRevision ) ) { + mUpdatingHistoryRefs = true; + mHistoryRefDropDown->getListView()->getSelection().set( + mHistoryRefModel->index( i, 0 ) ); + mUpdatingHistoryRefs = false; + return; + } + } +} + +void GitPlugin::updateHistoryRefs( const std::shared_ptr& model ) { + if ( !mHistoryRefDropDown ) + return; + std::string currentBranch; { const std::string repo = repoSelected(); Lock l( mGitBranchMutex ); auto it = mGitBranches.find( repo ); if ( it != mGitBranches.end() ) - branch = it->second; + currentBranch = it->second; } - mHistoryRefText->setText( branch.empty() ? "HEAD" : branch ); + String headLabel{ "HEAD" }; + if ( !currentBranch.empty() ) + headLabel = String::fromUtf8( String::format( "HEAD · %s", currentBranch ) ); + auto historyRefModel = std::make_shared( model, std::move( headLabel ) ); + size_t selected = 0; + bool found = false; + for ( size_t i = 0; i < historyRefModel->rowCount(); ++i ) { + if ( historyRefModel->isRevision( i, mHistoryRevision ) ) { + selected = i; + found = true; + break; + } + } + const bool revisionChanged = !found && mHistoryRevision != "HEAD"; + if ( !found ) + mHistoryRevision = "HEAD"; + mUpdatingHistoryRefs = true; + mHistoryRefModel = std::move( historyRefModel ); + mHistoryRefDropDown->setModel( mHistoryRefModel ); + mHistoryRefDropDown->getListView()->getSelection().set( + mHistoryRefModel->index( selected, 0 ) ); + mUpdatingHistoryRefs = false; + if ( revisionChanged ) + invalidateHistory(); } void GitPlugin::ensureHistoryLoaded() { @@ -2288,6 +2399,7 @@ void GitPlugin::reloadHistory() { mHistoryTree->clearViewMetadata(); mHistoryModel->setRootLoading(); Git::HistoryQuery query; + query.revision = mHistoryRevision; auto git = mGit; const auto lifetime = mLifetime.weakHandle(); ++mRunningHistoryRequests; @@ -2401,7 +2513,8 @@ void GitPlugin::openCommitDetails( const Git::Commit& commit ) { + font-size="14dp" word-wrap="false" text-overflow="ellipsis" + focusable="false" /> mCommitDetailsSubject->setText( + String::fromUtf8( subject.empty() ? commit.subject : std::string{ subject } ) ); + std::string body; + if ( subjectEnd != std::string::npos ) { + size_t bodyStart = subjectEnd; + while ( bodyStart < message.size() && + ( message[bodyStart] == '\n' || message[bodyStart] == '\r' ) ) + ++bodyStart; + body = message.substr( bodyStart ); } plugin->mCommitDetailsMessageBody = std::move( body ); plugin->mCommitDetailsMessage->setText( @@ -2638,20 +2759,26 @@ void GitPlugin::updateBranches( bool force ) { return; if ( !mGit || mGit->getGitFolder().empty() ) { - getUISceneNode()->runOnMainThread( [this] { updateBranchesUI( nullptr ); } ); + mLifetime.weakHandle().run( + []( GitPlugin* plugin ) { plugin->updateBranchesUI( nullptr ); } ); return; } + const std::string requestedRepo = repoSelected(); + const auto lifetime = mLifetime.weakHandle(); mRunningUpdateBranches++; mThreadPool->run( - [this] { + [this, requestedRepo, lifetime] { if ( !mGit || mGit->getGitFolder().empty() ) { - getUISceneNode()->runOnMainThread( [this] { updateBranchesUI( nullptr ); } ); + lifetime.run( [requestedRepo]( GitPlugin* plugin ) { + if ( requestedRepo == plugin->repoSelected() ) + plugin->updateBranchesUI( nullptr ); + } ); return; } auto prevBranch = updateReposBranches(); - auto branches = mGit->getAllBranchesAndTags( Git::RefType::All, {}, repoSelected() ); + auto branches = mGit->getAllBranchesAndTags( Git::RefType::All, {}, requestedRepo ); auto hash = GitBranchModel::hashBranches( branches ); auto model = GitBranchModel::asModel( std::move( branches ), hash, this ); @@ -2660,23 +2787,26 @@ void GitPlugin::updateBranches( bool force ) { Lock l( mGitBranchMutex ); branchChanged = prevBranch != mGitBranches; } - if ( mBranchesTree && mBranchesTree->getModel() && - static_cast( mBranchesTree->getModel() )->getHash() == hash ) { - if ( branchChanged ) { - getUISceneNode()->runOnMainThread( [this] { - if ( mBranchesTree && mBranchesTree->getModel() ) - mBranchesTree->getModel()->invalidate( Model::DontInvalidateIndexes ); - updateHistoryHeader(); - invalidateHistory(); - } ); + lifetime.run( [model, hash, branchChanged, requestedRepo]( GitPlugin* plugin ) { + if ( requestedRepo != plugin->repoSelected() ) + return; + if ( plugin->mBranchesTree && plugin->mBranchesTree->getModel() && + static_cast( plugin->mBranchesTree->getModel() )->getHash() == + hash ) { + if ( plugin->mBranchesTree->getModel() ) { + if ( branchChanged ) + plugin->mBranchesTree->getModel()->invalidate( + Model::DontInvalidateIndexes ); + plugin->updateHistoryRefs( std::static_pointer_cast( + plugin->mBranchesTree->getModelShared() ) ); + } + if ( branchChanged ) + plugin->invalidateHistory(); + } else { + plugin->updateBranchesUI( model ); + if ( branchChanged ) + plugin->invalidateHistory(); } - return; - } - - getUISceneNode()->runOnMainThread( [this, model, branchChanged] { - updateBranchesUI( model ); - if ( branchChanged ) - invalidateHistory(); } ); }, [this]( auto ) { mRunningUpdateBranches--; } ); @@ -2706,7 +2836,6 @@ void GitPlugin::updateRepos() { void GitPlugin::updateBranchesUI( std::shared_ptr model ) { buildSidePanelTab(); - updateHistoryHeader(); if ( !model ) { mBranchesTree->setModel( model ); @@ -2716,6 +2845,7 @@ void GitPlugin::updateBranchesUI( std::shared_ptr model ) { mBranchesTree->expandAll(); } + updateHistoryRefs( model ); updateRepos(); std::vector items; @@ -2838,7 +2968,7 @@ void GitPlugin::buildSidePanelTab() { - + @@ -2868,7 +2998,7 @@ void GitPlugin::buildSidePanelTab() { mTabContents->bind( "git_branches_tree", mBranchesTree ); mTabContents->bind( "git_status_tree", mStatusTree ); mTabContents->bind( "git_history_tree", mHistoryTree ); - mTabContents->bind( "git_history_ref", mHistoryRefText ); + mTabContents->bind( "git_history_ref", mHistoryRefDropDown ); mTabContents->bind( "git_content", mGitContentView ); mTabContents->bind( "git_no_content", mGitNoContentView ); mTabContents->bind( "git_conflict_state", mConflictStateBar ); @@ -2880,7 +3010,24 @@ void GitPlugin::buildSidePanelTab() { mTabContents->find( "branch_push" )->onClick( [this]( auto ) { push( repoSelected() ); } ); mTabContents->find( "branch_add" )->onClick( [this]( auto ) { branchCreate(); } ); mTabContents->find( "git_history_refresh" )->onClick( [this]( auto ) { reloadHistory(); } ); - updateHistoryHeader(); + mHistoryRefDropDown->getListView()->setColumnsVisible( { 0 } ); + mHistoryRefDropDown->getListView()->setAutoExpandOnSingleColumn( true ); + mHistoryRefDropDown->on( Event::OnItemSelected, [this]( const Event* ) { + if ( mUpdatingHistoryRefs ) + return; + const ModelIndex selected = mHistoryRefDropDown->getListView()->getSelection().first(); + if ( !selected.isValid() || !mHistoryRefModel ) + return; + const std::string revision = mHistoryRefModel->revision( selected.row() ); + if ( revision == mHistoryRevision ) + return; + mHistoryRevision = revision; + invalidateHistory(); + } ); + updateHistoryRefs( + mBranchesTree && mBranchesTree->getModel() + ? std::static_pointer_cast( mBranchesTree->getModelShared() ) + : nullptr ); mTabContents->find( "git_conflict_continue" )->onClick( [this]( auto ) { continueConflictOperation(); } ); @@ -2954,8 +3101,12 @@ void GitPlugin::buildSidePanelTab() { mPanelSwicher->on( Event::OnItemSelected, [this, listBox]( const Event* ) { mStackWidget->setActiveWidget( mStackMap[listBox->getItemSelectedIndex()] ); - if ( listBox->getItemSelectedIndex() == 2 ) + if ( listBox->getItemSelectedIndex() == 2 ) { + if ( mBranchesTree && mBranchesTree->getModel() ) + updateHistoryRefs( + std::static_pointer_cast( mBranchesTree->getModelShared() ) ); ensureHistoryLoaded(); + } } ); listBox->setSelected( 0 ); mStackWidget->setActiveWidget( mStackMap[0] ); @@ -3178,6 +3329,8 @@ void GitPlugin::buildSidePanelTab() { Lock l( mRepoMutex ); mRepoSelected = repo.first; } + mHistoryRevision = "HEAD"; + updateHistoryRefs( nullptr ); invalidateHistory(); updateBranches( true ); updateStatus( true ); diff --git a/src/tools/ecode/plugins/git/gitplugin.hpp b/src/tools/ecode/plugins/git/gitplugin.hpp index 081c8af44..2511fc538 100644 --- a/src/tools/ecode/plugins/git/gitplugin.hpp +++ b/src/tools/ecode/plugins/git/gitplugin.hpp @@ -20,6 +20,7 @@ using namespace EE::Scene; namespace EE::UI { class UITreeView; class UIDropDownList; +class UIDropDownModelList; class UIStackWidget; class UIListBoxItem; class UIMenu; @@ -31,6 +32,28 @@ namespace ecode { class Git; class GitBranchModel; +class GitHistoryRefModel : public Model { + public: + explicit GitHistoryRefModel( std::shared_ptr source, String headLabel ); + + size_t rowCount( const ModelIndex& = {} ) const override { return mSourceIndexes.size() + 1; } + + size_t columnCount( const ModelIndex& = {} ) const override { return 1; } + + ModelIndex index( int row, int column, const ModelIndex& parent = {} ) const override; + + Variant data( const ModelIndex& index, ModelRole role = ModelRole::Display ) const override; + + std::string revision( size_t index ) const; + + bool isRevision( size_t index, std::string_view revision ) const; + + private: + std::shared_ptr mSource; + std::vector mSourceIndexes; + String mHeadLabel; +}; + static constexpr const char* GIT_EMPTY = ""; static constexpr const char* GIT_SUCCESS = "success"; static constexpr const char* GIT_ERROR = "error"; @@ -125,7 +148,8 @@ class GitPlugin : public PluginBase { UITreeView* mBranchesTree{ nullptr }; UITreeView* mStatusTree{ nullptr }; UITreeView* mHistoryTree{ nullptr }; - UITextView* mHistoryRefText{ nullptr }; + UIDropDownModelList* mHistoryRefDropDown{ nullptr }; + std::shared_ptr mHistoryRefModel; std::shared_ptr mHistoryModel; UIDropDownList* mPanelSwicher{ nullptr }; UIDropDownList* mRepoDropDown{ nullptr }; @@ -140,6 +164,8 @@ class GitPlugin : public PluginBase { std::atomic mRunningHistoryRequests{ 0 }; std::atomic mHistoryGeneration{ 0 }; std::string mHistoryRepo; + std::string mHistoryRevision{ "HEAD" }; + bool mUpdatingHistoryRefs{ false }; bool mHistoryLoaded{ false }; UIWidget* mCommitDetailsView{ nullptr }; UITextView* mCommitDetailsSubject{ nullptr }; @@ -286,6 +312,8 @@ class GitPlugin : public PluginBase { void updateHistoryHeader(); + void updateHistoryRefs( const std::shared_ptr& model ); + void reloadHistory(); void invalidateHistory(); diff --git a/src/tools/ecode/plugins/lsp/lspclientplugin.cpp b/src/tools/ecode/plugins/lsp/lspclientplugin.cpp index 19029382e..03fea5727 100644 --- a/src/tools/ecode/plugins/lsp/lspclientplugin.cpp +++ b/src/tools/ecode/plugins/lsp/lspclientplugin.cpp @@ -283,7 +283,7 @@ Plugin* LSPClientPlugin::NewSync( PluginManager* pluginManager ) { } LSPClientPlugin::LSPClientPlugin( PluginManager* pluginManager, bool sync ) : - Plugin( pluginManager ) { + Plugin( pluginManager ), mLifetime( this, getUISceneNode() ) { if ( sync ) { load( pluginManager ); } else { @@ -292,6 +292,7 @@ LSPClientPlugin::LSPClientPlugin( PluginManager* pluginManager, bool sync ) : } LSPClientPlugin::~LSPClientPlugin() { + mLifetime.invalidate(); waitUntilLoaded(); mShuttingDown = true; mManager->unsubscribeMessages( this ); @@ -408,9 +409,11 @@ PluginRequestHandle LSPClientPlugin::processDocumentFormatting( const PluginMess auto ret = server.server->documentFormatting( server.uri, msg.asJSON()["options"], - [this, server]( const PluginIDType&, const std::vector& edits ) { - mManager->getSplitter()->getUISceneNode()->runOnMainThread( - [this, server, edits] { processDocumentFormattingResponse( server.uri, edits ); } ); + [lifetime = mLifetime.weakHandle(), server]( const PluginIDType&, + const std::vector& edits ) { + lifetime.run( [server, edits]( LSPClientPlugin* plugin ) { + plugin->processDocumentFormattingResponse( server.uri, edits ); + } ); } ); return ret; @@ -922,6 +925,7 @@ PluginRequestHandle LSPClientPlugin::processMessage( const PluginMessage& msg ) break; } case ecode::PluginMessageType::UIReady: { + mLifetime.setDispatcher( getUISceneNode() ); if ( mBrokenUserConfigFile ) displayBrokenUserConfigFileWarning(); break; diff --git a/src/tools/ecode/plugins/lsp/lspclientplugin.hpp b/src/tools/ecode/plugins/lsp/lspclientplugin.hpp index c3520b5f0..ce8d80c28 100644 --- a/src/tools/ecode/plugins/lsp/lspclientplugin.hpp +++ b/src/tools/ecode/plugins/lsp/lspclientplugin.hpp @@ -5,6 +5,7 @@ #include "../pluginmanager.hpp" #include "lspclientservermanager.hpp" #include +#include #include #include #include @@ -106,8 +107,10 @@ class LSPClientPlugin : public Plugin { const Float& fontSize ); protected: + MainThreadLifetime mLifetime; friend class LSPDocumentClient; friend class LSPClientServer; + friend class LSPClientServerManager; Clock mClock; Mutex mDocMutex; diff --git a/src/tools/ecode/plugins/lsp/lspclientservermanager.cpp b/src/tools/ecode/plugins/lsp/lspclientservermanager.cpp index bfc33b384..aef5f96b6 100644 --- a/src/tools/ecode/plugins/lsp/lspclientservermanager.cpp +++ b/src/tools/ecode/plugins/lsp/lspclientservermanager.cpp @@ -203,7 +203,7 @@ void LSPClientServerManager::executeCommand( const std::shared_ptr void LSPClientServerManager::applyWorkspaceEdit( const LSPWorkspaceEdit& edit, const std::function& resCb ) { - mPluginManager->getSplitter()->getUISceneNode()->runOnMainThread( [this, edit, resCb] { + mPlugin->mLifetime.weakHandle().run( [this, edit, resCb]( LSPClientPlugin* ) { bool allDone = true; for ( const auto& ed : edit.changes ) { @@ -279,10 +279,9 @@ void LSPClientServerManager::rangeFormatting( std::shared_ptr doc server->documentRangeFormatting( uri, doc->getSelection(), getURIJSON( doc ), [this, uri]( const PluginIDType&, const std::vector& edits ) { - mPluginManager->getSplitter()->getUISceneNode()->runOnMainThread( - [this, edits, uri] { - mPlugin->processDocumentFormattingResponse( uri, edits ); - } ); + mPlugin->mLifetime.weakHandle().run( [edits, uri]( LSPClientPlugin* plugin ) { + plugin->processDocumentFormattingResponse( uri, edits ); + } ); } ); } } ); diff --git a/src/tools/ecode/projectdirectorytree.cpp b/src/tools/ecode/projectdirectorytree.cpp index 5851a86bf..870f64e9a 100644 --- a/src/tools/ecode/projectdirectorytree.cpp +++ b/src/tools/ecode/projectdirectorytree.cpp @@ -19,11 +19,13 @@ ProjectDirectoryTree::ProjectDirectoryTree( mClosing( false ), mIgnoreMatcher( path ), mPluginManager( pluginManager ), - mLoadFileFromPathOrFocusFn( std::move( loadFileFromPathOrFocusFn ) ) { + mLoadFileFromPathOrFocusFn( std::move( loadFileFromPathOrFocusFn ) ), + mLifetime( this, pluginManager ? pluginManager->getUISceneNode() : nullptr ) { FileSystem::dirAddSlashAtEnd( mPath ); } ProjectDirectoryTree::~ProjectDirectoryTree() { + mLifetime.invalidate(); mClosing = true; if ( mPluginManager ) mPluginManager->unsubscribeMessages( "ProjectDirectoryTree" ); @@ -691,8 +693,9 @@ PluginRequestHandle ProjectDirectoryTree::processMessage( const PluginMessage& m if ( !matchesMap.empty() ) { if ( mPluginManager && mLoadFileFromPathOrFocusFn ) { std::string filePath( matchesMap.begin()->second ); - mPluginManager->getUISceneNode()->runOnMainThread( - [this, filePath]() { mLoadFileFromPathOrFocusFn( filePath ); } ); + mLifetime.weakHandle().run( [filePath]( ProjectDirectoryTree* tree ) { + tree->mLoadFileFromPathOrFocusFn( filePath ); + } ); } } diff --git a/src/tools/ecode/projectdirectorytree.hpp b/src/tools/ecode/projectdirectorytree.hpp index c560f5db6..bf9f68ce9 100644 --- a/src/tools/ecode/projectdirectorytree.hpp +++ b/src/tools/ecode/projectdirectorytree.hpp @@ -3,6 +3,7 @@ #include "ignorematcher.hpp" #include "plugins/pluginmanager.hpp" +#include #include #include #include @@ -189,6 +190,7 @@ class ProjectDirectoryTree { IgnoreMatcherManager mIgnoreMatcher; PluginManager* mPluginManager{ nullptr }; std::function mLoadFileFromPathOrFocusFn; + MainThreadLifetime mLifetime; void getDirectoryFiles( std::vector& files, std::vector& names, std::string directory, std::set currentDirs, diff --git a/src/tools/ecode/settingsactions.cpp b/src/tools/ecode/settingsactions.cpp index ab8d29a21..0fa44c432 100644 --- a/src/tools/ecode/settingsactions.cpp +++ b/src/tools/ecode/settingsactions.cpp @@ -4,6 +4,9 @@ namespace ecode { +SettingsActions::SettingsActions( App* app ) : + mApp( app ), mLifetime( this, app ? app->getUISceneNode() : nullptr ) {} + void SettingsActions::checkForUpdatesResponse( Http::Response&& response, bool fromStartup ) { auto updatesError = [this, fromStartup]() { if ( fromStartup ) @@ -97,13 +100,15 @@ void SettingsActions::checkForUpdatesResponse( Http::Response&& response, bool f } void SettingsActions::checkForUpdates( bool fromStartup ) { + mLifetime.setDispatcher( mApp->getUISceneNode() ); Http::getAsync( [this, fromStartup]( const Http&, Http::Request&, Http::Response& response ) { if ( !SceneManager::isActive() ) return; - mApp->getUISceneNode()->runOnMainThread( [this, res = response, fromStartup]() mutable { - checkForUpdatesResponse( std::move( res ), fromStartup ); - } ); + mLifetime.weakHandle().run( + [res = response, fromStartup]( SettingsActions* actions ) mutable { + actions->checkForUpdatesResponse( std::move( res ), fromStartup ); + } ); }, "https://api.github.com/repos/SpartanJ/ecode/releases/latest", Seconds( 30 ) ); } diff --git a/src/tools/ecode/settingsactions.hpp b/src/tools/ecode/settingsactions.hpp index 165f38315..c85e341c1 100644 --- a/src/tools/ecode/settingsactions.hpp +++ b/src/tools/ecode/settingsactions.hpp @@ -2,6 +2,7 @@ #define SETTINGSACTIONS_HPP #include +#include namespace ecode { @@ -9,7 +10,7 @@ class App; class SettingsActions { public: - explicit SettingsActions( App* app ) : mApp( app ) {} + explicit SettingsActions( App* app ); void checkForUpdates( bool fromStartup = false ); @@ -29,6 +30,7 @@ class SettingsActions { private: App* mApp{ nullptr }; + MainThreadLifetime mLifetime; String i18n( const std::string& key, const String& def ); diff --git a/src/tools/ecode/settingspanel.cpp b/src/tools/ecode/settingspanel.cpp index 0caf9eb42..d1d910da0 100644 --- a/src/tools/ecode/settingspanel.cpp +++ b/src/tools/ecode/settingspanel.cpp @@ -308,7 +308,8 @@ static void disableTabFocusTree( Node* node ) { disableTabFocusTree( child ); } -SettingsPanel::SettingsPanel( App* app ) : mApp( app ) {} +SettingsPanel::SettingsPanel( App* app ) : + mApp( app ), mLifetime( this, app ? app->getUISceneNode() : nullptr ) {} SettingsPanel::PanelState& SettingsPanel::state( Scope scope ) { return scope == Scope::User ? mUser : mProject; @@ -2107,8 +2108,9 @@ void SettingsPanel::addPluginSettings( PanelState& panel ) { auto* window = panel.window; window->runOnMainThread( [this, window, path] { window->closeWindow(); - mApp->getUISceneNode()->runOnMainThread( - [this, path] { mApp->focusOrLoadFile( path ); } ); + mLifetime.weakHandle().run( [path]( SettingsPanel* panel ) { + panel->mApp->focusOrLoadFile( path ); + } ); } ); } ); panel.documents.emplace_back( std::move( document ) ); diff --git a/src/tools/ecode/settingspanel.hpp b/src/tools/ecode/settingspanel.hpp index 390dba40d..8463030a1 100644 --- a/src/tools/ecode/settingspanel.hpp +++ b/src/tools/ecode/settingspanel.hpp @@ -3,6 +3,7 @@ #include "settingsmodel.hpp" #include +#include #include #include #include @@ -59,6 +60,7 @@ class SettingsPanel { }; App* mApp{ nullptr }; + MainThreadLifetime mLifetime; PanelState mUser; PanelState mProject; diff --git a/src/tools/ecode/statusbuildoutputcontroller.cpp b/src/tools/ecode/statusbuildoutputcontroller.cpp index c72345e50..dc50104a6 100644 --- a/src/tools/ecode/statusbuildoutputcontroller.cpp +++ b/src/tools/ecode/statusbuildoutputcontroller.cpp @@ -10,7 +10,7 @@ namespace ecode { StatusBuildOutputController::StatusBuildOutputController( UISplitter* mainSplitter, UISceneNode* uiSceneNode, PluginContextProvider* pluginContext ) : - StatusBarElement( mainSplitter, uiSceneNode, pluginContext ) {} + StatusBarElement( mainSplitter, uiSceneNode, pluginContext ), mLifetime( this, uiSceneNode ) {} static std::string getProjectOutputParserTypeToString( const ProjectOutputParserTypes& type ) { switch ( type ) { @@ -286,8 +286,8 @@ void StatusBuildOutputController::runBuild( const std::string& buildName, updateBuildButton(); if ( !mContext->getWindow()->hasFocus() ) { - mContext->getUISceneNode()->runOnMainThread( [this] { - mContext->getWindow()->flash( WindowFlashOperation::UntilFocused ); + mLifetime.weakHandle().run( []( StatusBuildOutputController* controller ) { + controller->mContext->getWindow()->flash( WindowFlashOperation::UntilFocused ); } ); } @@ -527,19 +527,25 @@ void StatusBuildOutputController::createContainer() { if ( !data.isValid() ) return; std::string path = data.toString(); - mUISceneNode->runOnMainThread( [this, path, lineNum, colNum] { - UITab* tab = mSplitter->isDocumentOpen( path ); - if ( !tab ) { - mContext->loadFileFromPath( - path, true, nullptr, - [this, lineNum, colNum]( auto, auto ) { - onLoadDone( lineNum, colNum ); - } ); - } else { - tab->getTabWidget()->setTabSelected( tab ); - onLoadDone( lineNum, colNum ); - } - } ); + mLifetime.weakHandle().run( + [path, lineNum, + colNum]( StatusBuildOutputController* controller ) { + UITab* tab = controller->mSplitter->isDocumentOpen( path ); + if ( !tab ) { + controller->mContext->loadFileFromPath( + path, true, nullptr, + [lifetime = controller->mLifetime.weakHandle(), + lineNum, colNum]( auto, auto ) { + lifetime.run( + [lineNum, colNum]( auto* loaded ) { + loaded->onLoadDone( lineNum, colNum ); + } ); + } ); + } else { + tab->getTabWidget()->setTabSelected( tab ); + controller->onLoadDone( lineNum, colNum ); + } + } ); } ); } } else { diff --git a/src/tools/ecode/statusbuildoutputcontroller.hpp b/src/tools/ecode/statusbuildoutputcontroller.hpp index 683fb8f78..e2f964957 100644 --- a/src/tools/ecode/statusbuildoutputcontroller.hpp +++ b/src/tools/ecode/statusbuildoutputcontroller.hpp @@ -3,6 +3,7 @@ #include "projectbuild.hpp" #include "uistatusbar.hpp" +#include #include #include #include @@ -40,7 +41,7 @@ class StatusBuildOutputController : public StatusBarElement { StatusBuildOutputController( UISplitter* mainSplitter, UISceneNode* uiSceneNode, PluginContextProvider* pluginContext ); - virtual ~StatusBuildOutputController() {}; + virtual ~StatusBuildOutputController() { mLifetime.invalidate(); }; void runBuild( const std::string& buildName, const std::string& buildType, const ProjectBuildOutputParser& outputParser = {}, bool isClean = false, @@ -73,6 +74,7 @@ class StatusBuildOutputController : public StatusBarElement { std::vector mPatternHolder; std::string mCurLineBuffer; bool mScrollLocked{ true }; + MainThreadLifetime mLifetime; void createContainer(); diff --git a/src/tools/ecode/universallocator.cpp b/src/tools/ecode/universallocator.cpp index d4ba06095..174c06639 100644 --- a/src/tools/ecode/universallocator.cpp +++ b/src/tools/ecode/universallocator.cpp @@ -185,6 +185,7 @@ UniversalLocator::UniversalLocator( UICodeEditorSplitter* editorSplitter, UIScen App* app ) : mSplitter( editorSplitter ), mUISceneNode( sceneNode ), + mLifetime( this, sceneNode ), mApp( app ), mCommandPalette( mApp->getThreadPool() ) { @@ -466,11 +467,12 @@ void UniversalLocator::updateFilesTable( bool useGlob ) { : ProjectDirectoryTree::MatchType::Fuzzy, text, LOCATEBAR_MAX_RESULTS, [this, text]( auto res ) { - mUISceneNode->runOnMainThread( [this, res] { - mLocateTable->setModel( res ); - mLocateTable->getSelection().set( mLocateTable->getModel()->index( 0 ) ); - mLocateTable->scrollToTop(); - updateLocateBarSync(); + mLifetime.weakHandle().run( [res]( UniversalLocator* locator ) { + locator->mLocateTable->setModel( res ); + locator->mLocateTable->getSelection().set( + locator->mLocateTable->getModel()->index( 0 ) ); + locator->mLocateTable->scrollToTop(); + locator->updateLocateBarSync(); } ); }, mApp->getCurrentProject() ); @@ -500,11 +502,12 @@ void UniversalLocator::updateCommandPaletteTable() { if ( txt.size() > 1 ) { mCommandPalette.asyncFuzzyMatch( txt.substr( 1 ).trim(), 10000, [this]( auto res ) { - mUISceneNode->runOnMainThread( [this, res] { - mLocateTable->setModel( res ); - if ( mLocateTable->getModel()->hasChildren() ) - mLocateTable->getSelection().set( mLocateTable->getModel()->index( 0 ) ); - mLocateTable->scrollToTop(); + mLifetime.weakHandle().run( [res]( UniversalLocator* locator ) { + locator->mLocateTable->setModel( res ); + if ( locator->mLocateTable->getModel()->hasChildren() ) + locator->mLocateTable->getSelection().set( + locator->mLocateTable->getModel()->index( 0 ) ); + locator->mLocateTable->scrollToTop(); } ); } ); } else if ( mCommandPalette.getCurModel() ) { @@ -575,15 +578,17 @@ bool UniversalLocator::tryLocator( const String& txt ) { query.trim(); const Uint64 generation = ++mLocatorModelGeneration; locator->modelFn( query, [this, generation]( std::shared_ptr model ) { - mUISceneNode->runOnMainThread( [this, generation, model = std::move( model )] { - if ( generation != mLocatorModelGeneration || !mLocateBarLayout->isVisible() ) - return; - mLocateTable->setModel( model ); - if ( model && model->hasChildren() ) - mLocateTable->getSelection().set( model->index( 0 ) ); - mLocateTable->scrollToTop(); - updateLocateBarSync(); - } ); + mLifetime.weakHandle().run( + [generation, model = std::move( model )]( UniversalLocator* locator ) { + if ( generation != locator->mLocatorModelGeneration || + !locator->mLocateBarLayout->isVisible() ) + return; + locator->mLocateTable->setModel( model ); + if ( model && model->hasChildren() ) + locator->mLocateTable->getSelection().set( model->index( 0 ) ); + locator->mLocateTable->scrollToTop(); + locator->updateLocateBarSync(); + } ); } ); return true; } @@ -1232,17 +1237,17 @@ void UniversalLocator::requestWorkspaceSymbol() { } void UniversalLocator::updateWorkspaceSymbol( const LSPSymbolInformationList& res ) { - mUISceneNode->runOnMainThread( [this, res] { - if ( !mWorkspaceSymbolModel ) { - mWorkspaceSymbolModel = - LSPSymbolInfoModel::create( mApp->getUISceneNode(), mWorkspaceSymbolQuery, res ); + mLifetime.weakHandle().run( [res]( UniversalLocator* locator ) { + if ( !locator->mWorkspaceSymbolModel ) { + locator->mWorkspaceSymbolModel = LSPSymbolInfoModel::create( + locator->mApp->getUISceneNode(), locator->mWorkspaceSymbolQuery, res ); } else { - mWorkspaceSymbolModel->setQuery( mWorkspaceSymbolQuery ); - mWorkspaceSymbolModel->append( res ); + locator->mWorkspaceSymbolModel->setQuery( locator->mWorkspaceSymbolQuery ); + locator->mWorkspaceSymbolModel->append( res ); } - mLocateTable->setModel( mWorkspaceSymbolModel ); - mLocateTable->getSelection().set( mLocateTable->getModel()->index( 0 ) ); - mLocateTable->scrollToTop(); + locator->mLocateTable->setModel( locator->mWorkspaceSymbolModel ); + locator->mLocateTable->getSelection().set( locator->mLocateTable->getModel()->index( 0 ) ); + locator->mLocateTable->scrollToTop(); } ); } @@ -1293,10 +1298,11 @@ void UniversalLocator::updateDocumentSymbol( const LSPSymbolInformationList& res } else { asyncFuzzyMatchTextDocumentSymbol( res, mCurDocQuery, 100, [this]( const auto model ) { mTextDocumentSymbolModel = model; - mUISceneNode->runOnMainThread( [this] { - mLocateTable->setModel( mTextDocumentSymbolModel ); - mLocateTable->getSelection().set( mLocateTable->getModel()->index( 0 ) ); - mLocateTable->scrollToTop(); + mLifetime.weakHandle().run( []( UniversalLocator* locator ) { + locator->mLocateTable->setModel( locator->mTextDocumentSymbolModel ); + locator->mLocateTable->getSelection().set( + locator->mLocateTable->getModel()->index( 0 ) ); + locator->mLocateTable->scrollToTop(); } ); } ); } diff --git a/src/tools/ecode/universallocator.hpp b/src/tools/ecode/universallocator.hpp index 95a0b9802..e09200121 100644 --- a/src/tools/ecode/universallocator.hpp +++ b/src/tools/ecode/universallocator.hpp @@ -6,6 +6,7 @@ #include "projectdirectorytree.hpp" #include "widgetcommandexecuter.hpp" #include +#include #include #include @@ -110,6 +111,7 @@ class UniversalLocator { UITextInput* mLocateInput{ nullptr }; UICodeEditorSplitter* mSplitter{ nullptr }; UISceneNode* mUISceneNode{ nullptr }; + MainThreadLifetime mLifetime; std::shared_ptr mWorkspaceSymbolModel{ nullptr }; std::string mWorkspaceSymbolQuery; App* mApp{ nullptr };