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 };