diff --git a/src/tools/ecode/ecode.cpp b/src/tools/ecode/ecode.cpp index 2d4d4a04c..98436b2c8 100644 --- a/src/tools/ecode/ecode.cpp +++ b/src/tools/ecode/ecode.cpp @@ -115,6 +115,8 @@ bool App::isAnyTerminalDirty() const { } bool App::onCloseRequestCallback( EE::Window::Window* ) { + if ( mClosing ) + return true; if ( mSplitter->isAnyEditorDirty() && ( !mConfig.workspace.sessionSnapshot || mCurrentProject.empty() ) ) { if ( mCloseMsgBox ) @@ -127,6 +129,7 @@ bool App::onCloseRequestCallback( EE::Window::Window* ) { mCloseMsgBox->on( Event::OnConfirm, [this]( const Event* ) { saveProject(); saveConfig(); + beginClosing(); mWindow->close(); } ); mCloseMsgBox->on( Event::OnWindowClose, [this]( auto ) { mCloseMsgBox = nullptr; } ); @@ -147,6 +150,7 @@ bool App::onCloseRequestCallback( EE::Window::Window* ) { mCloseMsgBox->on( Event::OnConfirm, [this]( const Event* ) { saveProject(); saveConfig(); + beginClosing(); mWindow->close(); } ); mCloseMsgBox->on( Event::OnWindowClose, [this]( auto ) { mCloseMsgBox = nullptr; } ); @@ -158,10 +162,19 @@ bool App::onCloseRequestCallback( EE::Window::Window* ) { } else { saveProject(); saveConfig(); + beginClosing(); return true; } } +void App::beginClosing() { + if ( mClosing ) + return; + mClosing = true; + if ( mPluginManager ) + mPluginManager->beginShutdown(); +} + void App::saveDoc() { if ( !mSplitter->curEditorExistsAndFocused() ) return; @@ -756,9 +769,16 @@ void App::initPluginManager() { onPluginEnabled( plugin ); } else { // If plugin loads asynchronously and is not ready, delay the plugin enabled callback - plugin->addOnReadyCallback( [this]( UICodeEditorPlugin* plugin, const Uint32& cbId ) { - mLifetime.weakHandle().run( [plugin]( App* app ) { - app->onPluginEnabled( static_cast( plugin ) ); + const std::string pluginId( plugin->getId() ); + plugin->addOnReadyCallback( [lifetime = mLifetime.weakHandle(), pluginId]( + UICodeEditorPlugin* plugin, const Uint32& cbId ) { + Plugin* readyPlugin = static_cast( plugin ); + lifetime.run( [pluginId, readyPlugin]( App* app ) { + Plugin* currentPlugin = + app->mPluginManager ? app->mPluginManager->get( pluginId ) : nullptr; + if ( currentPlugin == readyPlugin && !app->mPluginManager->isClosing() && + currentPlugin->isReady() ) + app->onPluginEnabled( currentPlugin ); } ); plugin->removeReadyCallback( cbId ); } ); @@ -911,6 +931,8 @@ std::shared_ptr App::getThreadPool() const { } bool App::trySendUnlockedCmd( const KeyEvent& keyEvent ) { + if ( mClosing || !mWindow || !mWindow->isRunning() ) + return false; if ( mSplitter->curEditorExistsAndFocused() ) { std::string cmd = mSplitter->getCurEditor()->getKeyBindings().getCommandFromKeyBind( { keyEvent.getKeyCode(), keyEvent.getMod() } ); diff --git a/src/tools/ecode/ecode.hpp b/src/tools/ecode/ecode.hpp index 2b1d4464e..461f87e46 100644 --- a/src/tools/ecode/ecode.hpp +++ b/src/tools/ecode/ecode.hpp @@ -740,6 +740,7 @@ class App : public UICodeEditorSplitter::Client, public PluginContextProvider { bool mFirstInstance{ false }; bool mPortableMode{ false }; bool mPortableModeFailed{ false }; + bool mClosing{ false }; bool mDestroyingApp{ false }; Time mFrameTime{ Time::Zero }; bool mIncognito{ false }; @@ -831,6 +832,8 @@ class App : public UICodeEditorSplitter::Client, public PluginContextProvider { bool onCloseRequestCallback( EE::Window::Window* ); + void beginClosing(); + void addRemainingTabWidgets( Node* widget ); void updateEditorState(); diff --git a/src/tools/ecode/plugins/aiassistant/aiassistantplugin.cpp b/src/tools/ecode/plugins/aiassistant/aiassistantplugin.cpp index 80b480d5b..6188da50a 100644 --- a/src/tools/ecode/plugins/aiassistant/aiassistantplugin.cpp +++ b/src/tools/ecode/plugins/aiassistant/aiassistantplugin.cpp @@ -269,12 +269,21 @@ AIAssistantPlugin::AIAssistantPlugin( PluginManager* pluginManager, bool sync ) } AIAssistantPlugin::~AIAssistantPlugin() { + { + std::lock_guard lock( mModelCatalogMutex ); + mModelCatalog.reset(); + } + + waitUntilLoaded(); + mShuttingDown = true; +} + +void AIAssistantPlugin::unregisterEditors() { { std::lock_guard lock( mModelCatalogMutex ); mModelCatalogCancelled->store( true ); if ( mModelCatalog ) mModelCatalog->cancel(); - mModelCatalog.reset(); } if ( SceneManager::existsSingleton() && !SceneManager::instance()->isShuttingDown() && getPluginContext() && getPluginContext()->getSplitter() ) { @@ -284,15 +293,17 @@ AIAssistantPlugin::~AIAssistantPlugin() { chat->setManager( nullptr ); } ); } - - waitUntilLoaded(); - mShuttingDown = true; - unsubscribeFileSystemListener(); - + if ( getPluginContext() && getPluginContext()->getMainLayout() ) { + getPluginContext()->getMainLayout()->unsetCommand( "new-ai-assistant" ); + getPluginContext()->getMainLayout()->getKeyBindings().removeCommandKeybind( + "new-ai-assistant" ); + } + PluginBase::unregisterEditors(); if ( mAIChatButton ) { if ( mAIChatButtonPosCbId ) mAIChatButton->getParent()->removeEventListener( mAIChatButtonPosCbId ); mAIChatButton->close(); + mAIChatButton = nullptr; } getPluginContext()->getConfig().removeTabWidgetType( "llm_chatui" ); } diff --git a/src/tools/ecode/plugins/aiassistant/aiassistantplugin.hpp b/src/tools/ecode/plugins/aiassistant/aiassistantplugin.hpp index a66d2df2f..f50cc22ff 100644 --- a/src/tools/ecode/plugins/aiassistant/aiassistantplugin.hpp +++ b/src/tools/ecode/plugins/aiassistant/aiassistantplugin.hpp @@ -93,6 +93,8 @@ class AIAssistantPlugin : public PluginBase { void onRegisterDocument( TextDocument* doc ) override; + void unregisterEditors() override; + void initUI(); void displayBrokenUserConfigFileWarning(); diff --git a/src/tools/ecode/plugins/autocomplete/autocompleteplugin.cpp b/src/tools/ecode/plugins/autocomplete/autocompleteplugin.cpp index c59c72488..1ea629af9 100644 --- a/src/tools/ecode/plugins/autocomplete/autocompleteplugin.cpp +++ b/src/tools/ecode/plugins/autocomplete/autocompleteplugin.cpp @@ -316,27 +316,10 @@ AutoCompletePlugin::AutoCompletePlugin( PluginManager* pluginManager, bool sync AutoCompletePlugin::~AutoCompletePlugin() { waitUntilLoaded(); mShuttingDown = true; - unregisterSnippetLocatorProvider(); - mManager->unsubscribeMessages( this ); - unsubscribeFileSystemListener(); while ( mSnippetJobs > 0 ) Sys::sleep( Milliseconds( 1 ) ); - for ( auto& client : mSnippetClients ) - client.second->detach(); - mSnippetClients.clear(); mSnippetSessions.clear(); - { - Lock l( mDocMutex ); - Lock l2( mLangSymbolsMutex ); - Lock l3( mSuggestionsMutex ); - for ( const auto& editor : mEditors ) { - for ( auto listener : editor.second ) - editor.first->removeEventListener( listener ); - editor.first->unregisterPlugin( this ); - } - } - bool isUpdating = false; do { { @@ -349,6 +332,15 @@ AutoCompletePlugin::~AutoCompletePlugin() { } while ( isUpdating ); } +void AutoCompletePlugin::unregisterEditors() { + unregisterSnippetLocatorProvider(); + while ( !mEditors.empty() ) + mEditors.begin()->first->unregisterPlugin( this ); + for ( auto& client : mSnippetClients ) + client.second->detach(); + mSnippetClients.clear(); +} + void AutoCompletePlugin::load( PluginManager* pluginManager ) { Clock clock; AtomicBoolScopedOp loading( mLoading, true ); @@ -834,7 +826,7 @@ void AutoCompletePlugin::onRegister( UICodeEditor* editor ) { } void AutoCompletePlugin::onUnregister( UICodeEditor* editor ) { - if ( mShuttingDown ) + if ( mShuttingDown && !mUnregistering ) return; if ( mSuggestionsEditor == editor ) resetSuggestions( editor ); @@ -854,6 +846,7 @@ void AutoCompletePlugin::onUnregister( UICodeEditor* editor ) { for ( auto ceditor : mEditorDocs ) if ( ceditor.second == doc ) return; + doc->removeCommand( "autocomplete-from-current-doc-symbols" ); detachSnippetClient( doc ); mDocs.erase( doc ); mDocCache.erase( doc ); diff --git a/src/tools/ecode/plugins/autocomplete/autocompleteplugin.hpp b/src/tools/ecode/plugins/autocomplete/autocompleteplugin.hpp index 4ef86fc8d..b35efd508 100644 --- a/src/tools/ecode/plugins/autocomplete/autocompleteplugin.hpp +++ b/src/tools/ecode/plugins/autocomplete/autocompleteplugin.hpp @@ -280,6 +280,8 @@ class AutoCompletePlugin : public Plugin { void load( PluginManager* pluginManager ); + void unregisterEditors() override; + void resetSuggestions( UICodeEditor* editor ); void updateSuggestions( const std::string& symbol, UICodeEditor* editor ); diff --git a/src/tools/ecode/plugins/debugger/debuggerplugin.cpp b/src/tools/ecode/plugins/debugger/debuggerplugin.cpp index 799955604..fd703ef7d 100644 --- a/src/tools/ecode/plugins/debugger/debuggerplugin.cpp +++ b/src/tools/ecode/plugins/debugger/debuggerplugin.cpp @@ -164,46 +164,11 @@ DebuggerPlugin::DebuggerPlugin( PluginManager* pluginManager, bool sync ) : } DebuggerPlugin::~DebuggerPlugin() { - mLifetime.invalidate(); waitUntilLoaded(); mShuttingDown = true; - { - Lock l( mClientsMutex ); - for ( const auto& client : mClients ) - client.first->unregisterClient( client.second.get() ); - } - - if ( mSidePanel && mTab ) { - if ( Engine::isMainThread() ) - mSidePanel->removeTab( mTab ); - else { - auto sidePanel = mSidePanel; - auto tab = mTab; - mSidePanel->runOnMainThread( [sidePanel, tab] { sidePanel->removeTab( tab ); } ); - } - } - - if ( getPluginContext()->getStatusBar() ) - getPluginContext()->getStatusBar()->removeStatusBarElement( "status_app_debugger" ); - - mManager->unsubscribeMessages( this ); - - for ( auto editor : mEditors ) { - onBeforeUnregister( editor.first ); - onUnregisterEditor( editor.first ); - } - mDebugger.reset(); mListener.reset(); - - if ( SceneManager::existsSingleton() && !SceneManager::instance()->isShuttingDown() && - getPluginContext() && getPluginContext()->getMainLayout() ) { - getPluginContext()->getMainLayout()->unsetCommands( mRegisteredCommands ); - - for ( const auto& kb : mKeyBindings ) - getPluginContext()->getMainLayout()->getKeyBindings().removeCommandKeybind( kb.first ); - } } void DebuggerPlugin::onSaveState( IniFile* state ) { @@ -1578,8 +1543,37 @@ void DebuggerPlugin::onRegisterDocument( TextDocument* doc ) { void DebuggerPlugin::onUnregisterDocument( TextDocument* doc ) { Lock l( mClientsMutex ); - doc->unregisterClient( mClients[doc].get() ); - mClients.erase( doc ); + auto client = mClients.find( doc ); + if ( client != mClients.end() ) { + doc->unregisterClient( client->second.get() ); + mClients.erase( client ); + } + PluginBase::onUnregisterDocument( doc ); + doc->removeCommand( "show-debugger-tab" ); +} + +void DebuggerPlugin::unregisterEditors() { + mLifetime.invalidate(); + PluginBase::unregisterEditors(); + { + Lock l( mClientsMutex ); + for ( const auto& client : mClients ) + client.first->unregisterClient( client.second.get() ); + mClients.clear(); + } + if ( mSidePanel && mTab ) { + mSidePanel->removeTab( mTab ); + mTab = nullptr; + } + if ( getPluginContext()->getStatusBar() ) + getPluginContext()->getStatusBar()->removeStatusBarElement( "status_app_debugger" ); + if ( SceneManager::existsSingleton() && !SceneManager::instance()->isShuttingDown() && + getPluginContext() && getPluginContext()->getMainLayout() ) { + getPluginContext()->getMainLayout()->unsetCommands( mRegisteredCommands ); + + for ( const auto& kb : mKeyBindings ) + getPluginContext()->getMainLayout()->getKeyBindings().removeCommandKeybind( kb.first ); + } } void DebuggerPlugin::onRegisterEditor( UICodeEditor* editor ) { diff --git a/src/tools/ecode/plugins/debugger/debuggerplugin.hpp b/src/tools/ecode/plugins/debugger/debuggerplugin.hpp index 1080b19c5..979576c6f 100644 --- a/src/tools/ecode/plugins/debugger/debuggerplugin.hpp +++ b/src/tools/ecode/plugins/debugger/debuggerplugin.hpp @@ -310,6 +310,8 @@ class DebuggerPlugin : public PluginBase { virtual void onUnregisterDocument( TextDocument* doc ) override; + void unregisterEditors() override; + void onDocumentLineMove( TextDocument* doc, const Int64& fromLine, const Int64& toLine, const Int64& numLines ); diff --git a/src/tools/ecode/plugins/discordRPC/discordRPCplugin.cpp b/src/tools/ecode/plugins/discordRPC/discordRPCplugin.cpp index bf8915f9a..e6a2467d4 100644 --- a/src/tools/ecode/plugins/discordRPC/discordRPCplugin.cpp +++ b/src/tools/ecode/plugins/discordRPC/discordRPCplugin.cpp @@ -62,11 +62,6 @@ DiscordRPCplugin::~DiscordRPCplugin() { if ( mIPC.isConnected() ) mIPC.clearActivity(); mShuttingDown = true; - - for ( auto editor : mEditors ) { - onBeforeUnregister( editor.first ); - onUnregisterEditor( editor.first ); - } } void DiscordRPCplugin::load( PluginManager* pluginManager ) { @@ -220,6 +215,11 @@ void DiscordRPCplugin::onUnregisterEditor( UICodeEditor* editor ) { editor->removeActionsByTag( DebounceUniqueId ); } +void DiscordRPCplugin::onUnregisterDocument( TextDocument* doc ) { + doc->removeCommand( "discordrpc-reconnect" ); + PluginBase::onUnregisterDocument( doc ); +} + void DiscordRPCplugin::updateActivity( DiscordIPCActivity& a ) { Lock l( mDataMutex ); Log::debug( "dcIPC: Activity in new file. lang = %s", mLastLang ); diff --git a/src/tools/ecode/plugins/discordRPC/discordRPCplugin.hpp b/src/tools/ecode/plugins/discordRPC/discordRPCplugin.hpp index f1e8613d5..50e19ce0b 100644 --- a/src/tools/ecode/plugins/discordRPC/discordRPCplugin.hpp +++ b/src/tools/ecode/plugins/discordRPC/discordRPCplugin.hpp @@ -71,6 +71,8 @@ class DiscordRPCplugin : public PluginBase { virtual void onUnregisterEditor( UICodeEditor* editor ) override; + virtual void onUnregisterDocument( TextDocument* doc ) override; + DiscordRPCplugin( PluginManager* pluginManager, bool sync ); void loadDiscordRPCConfig( const std::string& path, bool updateConfigFile ); diff --git a/src/tools/ecode/plugins/formatter/formatterplugin.cpp b/src/tools/ecode/plugins/formatter/formatterplugin.cpp index ab8cea7ae..16c4fc44e 100644 --- a/src/tools/ecode/plugins/formatter/formatterplugin.cpp +++ b/src/tools/ecode/plugins/formatter/formatterplugin.cpp @@ -48,23 +48,16 @@ FormatterPlugin::FormatterPlugin( PluginManager* pluginManager, bool sync ) : FormatterPlugin::~FormatterPlugin() { waitUntilLoaded(); mShuttingDown = true; - unsubscribeFileSystemListener(); if ( mWorkersCount != 0 ) { std::unique_lock lock( mWorkMutex ); mWorkerCondition.wait( lock, [this]() { return mWorkersCount <= 0; } ); } +} - for ( auto& editor : mEditors ) { - for ( auto& kb : mKeyBindings ) { - editor.first->getKeyBindings().removeCommandKeybind( kb.first ); - if ( editor.first->hasDocument() ) - editor.first->getDocument().removeCommand( kb.first ); - } - for ( auto listener : editor.second ) - editor.first->removeEventListener( listener ); - editor.first->unregisterPlugin( this ); - } +void FormatterPlugin::unregisterEditors() { + while ( !mEditors.empty() ) + mEditors.begin()->first->unregisterPlugin( this ); } void FormatterPlugin::onRegister( UICodeEditor* editor ) { @@ -115,8 +108,10 @@ void FormatterPlugin::onUnregister( UICodeEditor* editor ) { auto cbs = mEditors[editor]; for ( auto listener : cbs ) editor->removeEventListener( listener ); + for ( auto& kb : mKeyBindings ) + editor->getKeyBindings().removeCommandKeybind( kb.first ); - if ( mShuttingDown ) + if ( mShuttingDown && !mUnregistering ) return; mEditors.erase( editor ); mEditorDocs.erase( editor ); @@ -126,11 +121,8 @@ void FormatterPlugin::onUnregister( UICodeEditor* editor ) { if ( editorIt.second == doc ) return; - for ( auto& kb : mKeyBindings ) { - editor->getKeyBindings().removeCommandKeybind( kb.first ); - if ( editor->hasDocument() ) - editor->getDocument().removeCommand( kb.first ); - } + for ( auto& kb : mKeyBindings ) + doc->removeCommand( kb.first ); } bool FormatterPlugin::getAutoFormatOnSave() const { diff --git a/src/tools/ecode/plugins/formatter/formatterplugin.hpp b/src/tools/ecode/plugins/formatter/formatterplugin.hpp index 7dd5dbc93..c315daf26 100644 --- a/src/tools/ecode/plugins/formatter/formatterplugin.hpp +++ b/src/tools/ecode/plugins/formatter/formatterplugin.hpp @@ -101,6 +101,8 @@ class FormatterPlugin : public Plugin { void load( PluginManager* pluginManager ); + virtual void unregisterEditors(); + void loadFormatterConfig( const std::string& path, bool updateConfigFile ); void formatDoc( UICodeEditor* editor ); diff --git a/src/tools/ecode/plugins/git/gitplugin.cpp b/src/tools/ecode/plugins/git/gitplugin.cpp index 5656c2bff..c5039f9a4 100644 --- a/src/tools/ecode/plugins/git/gitplugin.cpp +++ b/src/tools/ecode/plugins/git/gitplugin.cpp @@ -210,27 +210,8 @@ GitPlugin::GitPlugin( PluginManager* pluginManager, bool sync ) : } GitPlugin::~GitPlugin() { - mLifetime.invalidate(); waitUntilLoaded(); mShuttingDown = true; - mCommitDetails.cancelDiffPreparation(); - mDetachedHistory.details.cancelDiffPreparation(); - mConflictViewCloseConnection.disconnect(); - mConflictView = nullptr; - mConflictSessions.clear(); - if ( mStatusButton ) - mStatusButton->close(); - - if ( mSidePanel && mTab ) - mSidePanel->removeTab( mTab ); - - endModelStyler(); - - if ( getUISceneNode() ) - getUISceneNode()->removeActionsByTag( GIT_STATUS_UPDATE_TAG ); - - if ( mStatusBar && mRepositionCbId ) - mStatusBar->removeEventListener( mRepositionCbId ); { Lock l( mGitBranchMutex ); @@ -259,6 +240,31 @@ GitPlugin::~GitPlugin() { Sys::sleep( Milliseconds( 1.f ) ); } +void GitPlugin::unregisterEditors() { + mLifetime.invalidate(); + mCommitDetails.cancelDiffPreparation(); + mDetachedHistory.details.cancelDiffPreparation(); + mConflictViewCloseConnection.disconnect(); + mConflictView = nullptr; + mConflictSessions.clear(); + if ( mStatusButton ) { + mStatusButton->close(); + mStatusButton = nullptr; + } + if ( mSidePanel && mTab ) { + mSidePanel->removeTab( mTab ); + mTab = nullptr; + } + endModelStyler(); + if ( getUISceneNode() ) + getUISceneNode()->removeActionsByTag( GIT_STATUS_UPDATE_TAG ); + if ( mStatusBar && mRepositionCbId ) { + mStatusBar->removeEventListener( mRepositionCbId ); + mRepositionCbId = 0; + } + PluginBase::unregisterEditors(); +} + void GitPlugin::onSaveState( IniFile* state ) { std::string commitMessage; Base64::encode( mLastCommitMsg.toUtf8(), commitMessage ); @@ -2413,7 +2419,16 @@ void GitPlugin::onRegister( UICodeEditor* editor ) { } void GitPlugin::onUnregister( UICodeEditor* editor ) { + TextDocument* doc = editor->getDocumentRef().get(); PluginBase::onUnregister( editor ); + if ( mDocs.find( doc ) == mDocs.end() ) { + doc->removeCommand( "show-source-control-tab" ); + doc->removeCommand( "git-pull" ); + doc->removeCommand( "git-push" ); + doc->removeCommand( "git-fetch" ); + doc->removeCommand( "git-commit" ); + doc->removeCommand( "git-show-history" ); + } } bool GitPlugin::onCreateContextMenu( UICodeEditor*, UIPopUpMenu* menu, const Vector2i& /*position*/, diff --git a/src/tools/ecode/plugins/git/gitplugin.hpp b/src/tools/ecode/plugins/git/gitplugin.hpp index be7fcb14c..660358360 100644 --- a/src/tools/ecode/plugins/git/gitplugin.hpp +++ b/src/tools/ecode/plugins/git/gitplugin.hpp @@ -97,6 +97,8 @@ class GitPlugin : public PluginBase { void onUnregister( UICodeEditor* ) override; + void unregisterEditors() override; + bool onCreateContextMenu( UICodeEditor* editor, UIPopUpMenu* menu, const Vector2i& position, const Uint32& flags ) override; diff --git a/src/tools/ecode/plugins/linter/linterplugin.cpp b/src/tools/ecode/plugins/linter/linterplugin.cpp index 18779c5c2..010e54c5c 100644 --- a/src/tools/ecode/plugins/linter/linterplugin.cpp +++ b/src/tools/ecode/plugins/linter/linterplugin.cpp @@ -85,8 +85,6 @@ LinterPlugin::LinterPlugin( PluginManager* pluginManager, bool sync ) : Plugin( LinterPlugin::~LinterPlugin() { waitUntilLoaded(); mShuttingDown = true; - mManager->unsubscribeMessages( this ); - unsubscribeFileSystemListener(); { std::lock_guard l( mRunningProcessesMutex ); @@ -98,17 +96,11 @@ LinterPlugin::~LinterPlugin() { std::unique_lock lock( mWorkMutex ); mWorkerCondition.wait( lock, [this]() { return mWorkersCount <= 0; } ); +} - for ( const auto& editor : mEditors ) { - for ( auto& kb : mKeyBindings ) { - editor.first->getKeyBindings().removeCommandKeybind( kb.first ); - if ( editor.first->hasDocument() ) - editor.first->getDocument().removeCommand( kb.first ); - } - for ( auto listener : editor.second ) - editor.first->removeEventListener( listener ); - editor.first->unregisterPlugin( this ); - } +void LinterPlugin::unregisterEditors() { + while ( !mEditors.empty() ) + mEditors.begin()->first->unregisterPlugin( this ); } size_t LinterPlugin::linterFilePatternPosition( const std::vector& patterns ) { @@ -753,7 +745,7 @@ void LinterPlugin::onRegister( UICodeEditor* editor ) { } void LinterPlugin::onUnregister( UICodeEditor* editor ) { - if ( mShuttingDown ) + if ( mShuttingDown && !mUnregistering ) return; Lock l( mDocMutex ); @@ -761,17 +753,17 @@ void LinterPlugin::onUnregister( UICodeEditor* editor ) { auto cbs = mEditors[editor]; for ( auto listener : cbs ) editor->removeEventListener( listener ); + for ( auto& kb : mKeyBindings ) + editor->getKeyBindings().removeCommandKeybind( kb.first ); mEditors.erase( editor ); mEditorDocs.erase( editor ); for ( auto editorIt : mEditorDocs ) if ( editorIt.second == doc ) return; - for ( auto& kb : mKeyBindings ) { - editor->getKeyBindings().removeCommandKeybind( kb.first ); - if ( editor->hasDocument() ) - editor->getDocument().removeCommand( kb.first ); - } + for ( auto& kb : mKeyBindings ) + doc->removeCommand( kb.first ); + doc->removeCommand( "linter-copy-error-message" ); mDocs.erase( doc ); mDirtyDoc.erase( doc ); diff --git a/src/tools/ecode/plugins/linter/linterplugin.hpp b/src/tools/ecode/plugins/linter/linterplugin.hpp index ca45f69fb..d6b5cae7a 100644 --- a/src/tools/ecode/plugins/linter/linterplugin.hpp +++ b/src/tools/ecode/plugins/linter/linterplugin.hpp @@ -170,6 +170,8 @@ class LinterPlugin : public Plugin { void load( PluginManager* pluginManager ); + virtual void unregisterEditors(); + void lintDoc( std::shared_ptr doc ); void runLinter( std::shared_ptr doc, const Linter& linter, diff --git a/src/tools/ecode/plugins/lsp/lspclientplugin.cpp b/src/tools/ecode/plugins/lsp/lspclientplugin.cpp index 03fea5727..1120b95f4 100644 --- a/src/tools/ecode/plugins/lsp/lspclientplugin.cpp +++ b/src/tools/ecode/plugins/lsp/lspclientplugin.cpp @@ -78,6 +78,23 @@ static Action::UniqueID getMouseMoveHash( UICodeEditor* editor ) { reinterpret_cast( editor ) ); } +static constexpr const char* LSPDocumentCommands[] = { + "lsp-go-to-definition", + "lsp-go-to-declaration", + "lsp-go-to-implementation", + "lsp-go-to-type-definition", + "lsp-switch-header-source", + "lsp-symbol-info", + "lsp-symbol-references", + "lsp-memory-usage", + "lsp-symbol-code-action", + "lsp-rename-symbol-under-cursor", + "lsp-refresh-semantic-highlighting", + "lsp-format-range", + "lsp-plugin-restart", + "lsp-show-document-symbols", +}; + static json getURIAndPositionJSON( UICodeEditor* editor ) { json data; auto doc = editor->getDocumentRef(); @@ -292,31 +309,15 @@ LSPClientPlugin::LSPClientPlugin( PluginManager* pluginManager, bool sync ) : } LSPClientPlugin::~LSPClientPlugin() { - mLifetime.invalidate(); waitUntilLoaded(); mShuttingDown = true; - mManager->unsubscribeMessages( this ); - unsubscribeFileSystemListener(); - { - Lock l( mDocMutex ); - for ( const auto& editor : mEditors ) { - UICodeEditor* codeEditor = editor.first; - for ( auto& kb : mKeyBindings ) { - codeEditor->getKeyBindings().removeCommandKeybind( kb.first ); - if ( codeEditor->hasDocument() ) - codeEditor->getDocument().removeCommand( kb.first ); - } - for ( auto listener : editor.second ) - codeEditor->removeEventListener( listener ); - if ( mBreadcrumb ) - codeEditor->unregisterTopSpace( this ); - codeEditor->unregisterPlugin( this ); - if ( mManager->getSplitter()->editorExists( codeEditor ) ) - codeEditor->removeActionsByTag( getMouseMoveHash( codeEditor ) ); - } - if ( nullptr == mManager->getSplitter() ) - return; - } +} + +void LSPClientPlugin::unregisterEditors() { + mLifetime.invalidate(); + while ( !mEditors.empty() ) + mEditors.begin()->first->unregisterPlugin( this ); + mClientManager.detachDocuments(); } void LSPClientPlugin::update( UICodeEditor* ) { @@ -1157,21 +1158,7 @@ void LSPClientPlugin::loadLSPConfig( std::vector& lsps, const std if ( j.contains( "keybindings" ) ) { auto& kb = j["keybindings"]; - auto list = { "lsp-go-to-definition", - "lsp-go-to-declaration", - "lsp-go-to-implementation", - "lsp-go-to-type-definition", - "lsp-switch-header-source", - "lsp-symbol-info", - "lsp-symbol-references", - "lsp-memory-usage", - "lsp-symbol-code-action", - "lsp-rename-symbol-under-cursor", - "lsp-refresh-semantic-highlighting", - "lsp-format-range", - "lsp-plugin-restart", - "lsp-show-document-symbols" }; - for ( const auto& key : list ) { + for ( const auto* key : LSPDocumentCommands ) { if ( kb.contains( key ) ) { if ( !kb[key].empty() ) mKeyBindings[key] = kb[key]; @@ -1525,7 +1512,10 @@ void LSPClientPlugin::onRegister( UICodeEditor* editor ) { static_cast( client )->getDocumentRef() ); } ); - doc.setCommand( "lsp-plugin-restart", [this] { mManager->reload( getId() ); } ); + doc.setCommand( "lsp-plugin-restart", [lifetime = mLifetime.weakHandle()] { + lifetime.run( + []( LSPClientPlugin* plugin ) { plugin->mManager->reload( plugin->getId() ); } ); + } ); doc.setCommand( "lsp-show-document-symbols", [this]( TextDocument::Client* client ) { showDocumentSymbols( static_cast( client ) ); @@ -1603,7 +1593,7 @@ void LSPClientPlugin::onUnregister( UICodeEditor* editor ) { for ( auto& kb : mKeyBindings ) editor->getKeyBindings().removeCommandKeybind( kb.first ); - if ( mShuttingDown ) + if ( mShuttingDown && !mUnregistering ) return; editor->removeActionsByTag( getMouseMoveHash( editor ) ); @@ -1628,8 +1618,8 @@ void LSPClientPlugin::onUnregister( UICodeEditor* editor ) { } if ( editor->hasDocument() ) - for ( auto& kb : mKeyBindings ) - editor->getDocument().removeCommand( kb.first ); + for ( const auto* command : LSPDocumentCommands ) + editor->getDocument().removeCommand( command ); { Lock lds( mDocSymbolsMutex ); diff --git a/src/tools/ecode/plugins/lsp/lspclientplugin.hpp b/src/tools/ecode/plugins/lsp/lspclientplugin.hpp index ce8d80c28..1554b15aa 100644 --- a/src/tools/ecode/plugins/lsp/lspclientplugin.hpp +++ b/src/tools/ecode/plugins/lsp/lspclientplugin.hpp @@ -162,6 +162,8 @@ class LSPClientPlugin : public Plugin { void load( PluginManager* pluginManager ); + virtual void unregisterEditors(); + void loadLSPConfig( std::vector& lsps, const std::string& path, bool updateConfigFile ); diff --git a/src/tools/ecode/plugins/lsp/lspclientserver.cpp b/src/tools/ecode/plugins/lsp/lspclientserver.cpp index c4b3a8b40..22c7f48ce 100644 --- a/src/tools/ecode/plugins/lsp/lspclientserver.cpp +++ b/src/tools/ecode/plugins/lsp/lspclientserver.cpp @@ -1289,11 +1289,6 @@ LSPClientServer::~LSPClientServer() { mProcess.kill(); eeSAFE_DELETE( mSocket ); - { - Lock l( mClientsMutex ); - for ( const auto& client : mClients ) - client.first->unregisterClient( client.second.get() ); - } } bool LSPClientServer::socketConnect() { @@ -1414,6 +1409,12 @@ bool LSPClientServer::registerDoc( const std::shared_ptr& doc ) { return true; } +void LSPClientServer::detachDocuments() { + Lock l( mClientsMutex ); + for ( const auto& client : mClients ) + client.second->detach(); +} + void LSPClientServer::notifyServerInitialized() { for ( const auto& client : mClients ) client.second->onServerInitialized(); diff --git a/src/tools/ecode/plugins/lsp/lspclientserver.hpp b/src/tools/ecode/plugins/lsp/lspclientserver.hpp index 7b20e8690..66e50a15d 100644 --- a/src/tools/ecode/plugins/lsp/lspclientserver.hpp +++ b/src/tools/ecode/plugins/lsp/lspclientserver.hpp @@ -75,6 +75,8 @@ class LSPClientServer { bool registerDoc( const std::shared_ptr& doc ); + void detachDocuments(); + bool isRunning(); bool isReady() const; diff --git a/src/tools/ecode/plugins/lsp/lspclientservermanager.cpp b/src/tools/ecode/plugins/lsp/lspclientservermanager.cpp index aef5f96b6..4834fa764 100644 --- a/src/tools/ecode/plugins/lsp/lspclientservermanager.cpp +++ b/src/tools/ecode/plugins/lsp/lspclientservermanager.cpp @@ -145,6 +145,12 @@ void LSPClientServerManager::closeLSPServer( const String::HashType& id ) { if ( mErasingClients.find( id ) != mErasingClients.end() ) return; mErasingClients.insert( id ); + { + Lock l( mClientsMutex ); + auto it = mClients.find( id ); + if ( it != mClients.end() ) + it->second->detachDocuments(); + } mThreadPool->run( [this, id]() { Lock l( mClientsMutex ); auto it = mClients.find( id ); @@ -158,6 +164,12 @@ void LSPClientServerManager::closeLSPServer( const String::HashType& id ) { } ); } +void LSPClientServerManager::detachDocuments() { + Lock l( mClientsMutex ); + for ( const auto& server : mClients ) + server.second->detachDocuments(); +} + void LSPClientServerManager::goToLocation( const LSPLocation& loc ) { UICodeEditorSplitter* splitter = mPlugin->getManager()->getSplitter(); if ( nullptr == splitter ) diff --git a/src/tools/ecode/plugins/lsp/lspclientservermanager.hpp b/src/tools/ecode/plugins/lsp/lspclientservermanager.hpp index 7b1917a2e..5bd82b036 100644 --- a/src/tools/ecode/plugins/lsp/lspclientservermanager.hpp +++ b/src/tools/ecode/plugins/lsp/lspclientservermanager.hpp @@ -25,6 +25,8 @@ class LSPClientServerManager { // sync void tryRunServer( const std::shared_ptr& doc ); + void detachDocuments(); + size_t clientCount() const; size_t lspCount() const; diff --git a/src/tools/ecode/plugins/lsp/lspdocumentclient.cpp b/src/tools/ecode/plugins/lsp/lspdocumentclient.cpp index 0e99f9240..15ab8398c 100644 --- a/src/tools/ecode/plugins/lsp/lspdocumentclient.cpp +++ b/src/tools/ecode/plugins/lsp/lspdocumentclient.cpp @@ -40,17 +40,27 @@ void LSPDocumentClient::setupFoldRangeService() { } LSPDocumentClient::~LSPDocumentClient() { - mDoc->getFoldRangeService().setProvider( nullptr ); - mDoc->unregisterClient( this ); + detach(); + while ( mRunningSemanticTokens || mProcessingSemanticTokensResponse ) + Sys::sleep( Milliseconds( 0.1f ) ); mDoc = nullptr; +} + +void LSPDocumentClient::detach() { + if ( mDetached ) + return; + mDetached = true; + mShutdown = true; + if ( mDoc ) { + if ( mDoc->getFoldRangeService().getProvider() == this ) + mDoc->getFoldRangeService().setProvider( nullptr ); + mDoc->unregisterClient( this ); + } UISceneNode* sceneNode = getUISceneNode(); if ( nullptr != sceneNode && 0 != mTag ) sceneNode->removeActionsByTag( mTag ); if ( nullptr != sceneNode && 0 != mTagSemanticTokens ) sceneNode->removeActionsByTag( mTagSemanticTokens ); - mShutdown = true; - while ( mRunningSemanticTokens || mProcessingSemanticTokensResponse ) - Sys::sleep( Milliseconds( 0.1f ) ); } bool LSPDocumentClient::tryRequestFoldRanges( bool requestFolds ) { diff --git a/src/tools/ecode/plugins/lsp/lspdocumentclient.hpp b/src/tools/ecode/plugins/lsp/lspdocumentclient.hpp index 9fd777ae6..f905bd7a0 100644 --- a/src/tools/ecode/plugins/lsp/lspdocumentclient.hpp +++ b/src/tools/ecode/plugins/lsp/lspdocumentclient.hpp @@ -26,6 +26,8 @@ class LSPDocumentClient : public TextDocument::Client, public FoldRangeProvider ~LSPDocumentClient(); + void detach(); + virtual void onDocumentLoaded( TextDocument* ); virtual void onDocumentTextChanged( const DocumentContentChange& change ); virtual void onDocumentUndoRedo( const TextDocument::UndoRedo& eventType ); @@ -86,6 +88,7 @@ class LSPDocumentClient : public TextDocument::Client, public FoldRangeProvider bool mWaitingSemanticTokensResponse{ false }; bool mProcessingSemanticTokensResponse{ false }; bool mShutdown{ false }; + bool mDetached{ false }; bool mFirstHighlight{ true }; bool mAlreadyRequestedFoldingRanges{ false }; diff --git a/src/tools/ecode/plugins/plugin.cpp b/src/tools/ecode/plugins/plugin.cpp index 9d8ad2965..ce21dcff7 100644 --- a/src/tools/ecode/plugins/plugin.cpp +++ b/src/tools/ecode/plugins/plugin.cpp @@ -27,6 +27,17 @@ bool Plugin::isShuttingDown() const { return mShuttingDown; } +void Plugin::shutdown() { + if ( mShuttingDown.exchange( true ) ) + return; + waitUntilLoaded(); + unsubscribeFileSystemListener(); + mManager->unsubscribeMessages( this ); + mUnregistering = true; + unregisterEditors(); + mUnregistering = false; +} + bool Plugin::hasFileConfig() { return !mConfigPath.empty(); } @@ -249,13 +260,6 @@ void Plugin::createListView( UICodeEditor* editor, std::shared_ptr model, PluginBase::~PluginBase() { mShuttingDown = true; - unsubscribeFileSystemListener(); - - for ( auto editor : mEditors ) { - for ( auto listener : editor.second ) - editor.first->removeEventListener( listener ); - editor.first->unregisterPlugin( this ); - } } void PluginBase::onRegister( UICodeEditor* editor ) { @@ -306,7 +310,7 @@ void PluginBase::onRegister( UICodeEditor* editor ) { void PluginBase::onUnregister( UICodeEditor* editor ) { onBeforeUnregister( editor ); - if ( mShuttingDown ) + if ( mShuttingDown && !mUnregistering ) return; Lock l( mMutex ); TextDocument* doc = mEditorDocs[editor]; @@ -342,4 +346,9 @@ void PluginBase::onUnregisterDocument( TextDocument* doc ) { doc->removeCommand( kb.first ); } +void PluginBase::unregisterEditors() { + while ( !mEditors.empty() ) + mEditors.begin()->first->unregisterPlugin( this ); +} + } // namespace ecode diff --git a/src/tools/ecode/plugins/plugin.hpp b/src/tools/ecode/plugins/plugin.hpp index 27407f7d5..31cc66b44 100644 --- a/src/tools/ecode/plugins/plugin.hpp +++ b/src/tools/ecode/plugins/plugin.hpp @@ -28,6 +28,32 @@ class PluginManager; class PluginContextProvider; class SettingsPage; +/** + * Base class for ecode plugins. + * + * Plugin teardown has two distinct phases: + * + * 1. PluginManager calls shutdown() synchronously on the main thread. shutdown() waits for + * loading to finish, prevents new work, unsubscribes manager-owned callbacks, and calls + * unregisterEditors() while the complete derived object is still alive. + * 2. PluginManager destroys the plugin. Normal disable/reload may run the destructor on a worker + * thread; application shutdown may run it on the main thread. + * + * Implementers must detach everything that can call into the plugin from unregisterEditors(): + * editor and document commands, keybindings, event listeners, document clients, application + * commands, UI widgets/actions, and queued main-thread callbacks. When unregisterEditors() + * returns, no application-owned object may retain a pointer or callback to the plugin. + * + * Destructors must only release plugin-owned resources. They may cancel or join workers and stop + * child processes or servers, but must not access editors, documents, UI objects, or PluginManager. + * Potentially blocking resource shutdown belongs in the destructor so normal unload does not + * stall the main thread. + * + * PluginManager owns the lifecycle: implementations must not call shutdown() themselves and must + * not defer application detachment to their destructor. PluginBase is the preferred base class + * when a plugin registers with editors or documents because it provides the common bookkeeping + * and cleanup hooks described below. + */ class Plugin : public UICodeEditorPlugin { public: explicit Plugin( PluginManager* manager ); @@ -42,6 +68,15 @@ class Plugin : public UICodeEditorPlugin { bool isShuttingDown() const; + /** + * Starts the main-thread application-detachment phase. + * + * This operation is idempotent and is called exclusively by PluginManager. It sets + * isShuttingDown(), waits for loading to complete, removes the plugin's filesystem and message + * subscriptions, and invokes unregisterEditors(). + */ + void shutdown(); + virtual bool hasFileConfig(); virtual std::string getFileConfigPath(); @@ -97,6 +132,7 @@ class Plugin : public UICodeEditorPlugin { std::atomic mReady{ false }; std::atomic mLoading{ false }; std::atomic mShuttingDown{ false }; + bool mUnregistering{ false }; void setReady( Time loadTime = Seconds( 0 ) ); @@ -104,14 +140,98 @@ class Plugin : public UICodeEditorPlugin { bool editorExists( UICodeEditor* editor ); + /** + * Detaches all application-owned references to this plugin. + * + * This is called by shutdown() on the main thread, with isShuttingDown() already true and while + * virtual dispatch still reaches the most-derived class. Implementations must be synchronous, + * must not wait for slow plugin-owned resources, and must leave no editor, document, UI, + * manager, or queued callback able to call the plugin. + * + * PluginBase subclasses overriding this method must call PluginBase::unregisterEditors() + * exactly once so its per-editor and per-document unregister hooks run. + */ + virtual void unregisterEditors() = 0; + static UIListView* createListViewHelper( UICodeEditor* editor, std::shared_ptr model, const ModelEventCallback& onModelEventCb ); }; +/** + * Plugin implementation helper for editor- and document-oriented plugins. + * + * PluginBase records every registered editor, document, and editor event listener. During + * shutdown, unregisterEditors() asks each editor to unregister the plugin. That invokes the + * following cleanup hooks while the derived plugin is still fully alive: + * + * - onBeforeUnregister(): removes keybindings declared in mKeyBindings; + * - onUnregisterEditor(): removes plugin-specific state owned by one editor; + * - onUnregisterDocument(): removes plugin commands and other per-document state after the last + * editor using that document is unregistered. + * + * The default onUnregisterDocument() removes commands named by mKeyBindings. A derived override + * that adds other commands must remove them and call PluginBase::onUnregisterDocument(). Event + * listener IDs added through onRegisterListeners() are removed automatically. + * + * Plugin implementer checklist: + * + * 1. Append every editor event listener ID to the vector passed to onRegisterListeners(). + * 2. Undo per-editor registrations in onUnregisterEditor(). + * 3. Undo per-document registrations in onUnregisterDocument(), then call the base implementation. + * 4. Override unregisterEditors() only for state not covered by those hooks: invalidate async + * callbacks, detach document clients, remove application commands, and close plugin UI. Call + * PluginBase::unregisterEditors() exactly once. + * 5. Keep only plugin-owned resource cleanup, such as joining workers or stopping servers, in the + * destructor. + * + * The unregister hooks are also called when individual editors and documents close, so they must + * work both during normal editor use and during full plugin shutdown. + * + * A typical implementation is: + * + * @code + * class ExamplePlugin : public PluginBase { + * public: + * ~ExamplePlugin() override { + * // No editor, document, UI, or PluginManager access here. This may block on a worker. + * mWorker.stopAndWait(); + * } + * + * protected: + * void onRegisterDocument( TextDocument* doc ) override { + * doc->setCommand( "example-command", [this] { runCommand(); } ); + * } + * + * void onRegisterListeners( UICodeEditor* editor, + * std::vector& listeners ) override { + * listeners.emplace_back( + * editor->on( Event::OnDocumentSave, [this]( const Event* ) { onSave(); } ) ); + * } + * + * void onUnregisterDocument( TextDocument* doc ) override { + * doc->removeCommand( "example-command" ); + * PluginBase::onUnregisterDocument( doc ); + * } + * + * void unregisterEditors() override { + * // Cancel/invalidate callbacks first, then detach application-owned state. + * mLifetime.invalidate(); + * removeApplicationCommand(); + * PluginBase::unregisterEditors(); + * closePluginUI(); + * } + * }; + * @endcode + * + * If no plugin-specific application state exists, do not override unregisterEditors(); the base + * implementation is sufficient. Never call onUnregisterEditor() or onUnregisterDocument() + * directly from the destructor. + */ class PluginBase : public Plugin { public: explicit PluginBase( PluginManager* manager ) : Plugin( manager ) {} + /** Destructor finalizes plugin-owned state only; application detachment is already complete. */ virtual ~PluginBase(); virtual void onRegister( UICodeEditor* ) override; @@ -144,17 +264,23 @@ class PluginBase : public Plugin { virtual void onRegisterListeners( UICodeEditor*, std::vector& /*listeners*/ ) {}; - //! Usually used to remove keybindings in an editor + //! Called before an editor is unregistered. The default removes mKeyBindings from the editor. virtual void onBeforeUnregister( UICodeEditor* ); + //! Registers commands and other state shared by all editors using the document. virtual void onRegisterDocument( TextDocument* ) {} + //! Registers state owned by one editor. The default installs mKeyBindings. virtual void onRegisterEditor( UICodeEditor* ); + //! Removes plugin-specific state owned by one editor. virtual void onUnregisterEditor( UICodeEditor* ) {} - //! Usually used to unregister commands in a document + //! Removes per-document state. The default removes commands named by mKeyBindings. virtual void onUnregisterDocument( TextDocument* ); + + //! Unregisters every tracked editor and dispatches the cleanup hooks documented above. + virtual void unregisterEditors() override; }; } // namespace ecode diff --git a/src/tools/ecode/plugins/pluginmanager.cpp b/src/tools/ecode/plugins/pluginmanager.cpp index 853fca5b5..b85de32e0 100644 --- a/src/tools/ecode/plugins/pluginmanager.cpp +++ b/src/tools/ecode/plugins/pluginmanager.cpp @@ -23,18 +23,27 @@ PluginManager::PluginManager( const std::string& resourcesPath, const std::strin mLoadFileFn( loadFileCb ) {} PluginManager::~PluginManager() { - mClosing = true; + beginShutdown(); for ( auto& plugin : mPlugins ) { Log::debug( "PluginManager: unloading plugin %s", plugin.second->getTitle() ); - eeDelete( plugin.second ); + unloadPlugin( plugin.second ); } - unsubscribeFileSystemListener(); + std::unique_lock lock( mPendingUnloadsMutex ); + mPendingUnloadsCondition.wait( lock, [this] { return mPendingUnloads == 0; } ); } bool PluginManager::isClosing() const { return mClosing; } +void PluginManager::beginShutdown() { + if ( mClosing ) + return; + mClosing = true; + mPluginReloadEnabled = false; + unsubscribeFileSystemListener(); +} + void PluginManager::registerPlugin( const PluginDefinition& def ) { mDefinitions[def.id] = def; } @@ -55,6 +64,8 @@ Plugin* ecode::PluginManager::get( const std::string& id ) { } bool PluginManager::setEnabled( const std::string& id, bool enable, bool sync ) { + if ( mClosing ) + return false; mPluginsEnabled[id] = enable; Plugin* plugin = get( id ); if ( enable && plugin == nullptr && hasDefinition( id ) ) { @@ -69,12 +80,8 @@ bool PluginManager::setEnabled( const std::string& id, bool enable, bool sync ) } if ( !enable && plugin != nullptr ) { Log::debug( "PluginManager: unloading plugin %s", mDefinitions[id].name ); - mThreadPool->run( [plugin]() { eeDelete( plugin ); } ); - { - Lock l( mSubscribedPluginsMutex ); - mSubscribedPlugins.erase( id ); - } mPlugins.erase( id ); + unloadPlugin( plugin ); } return false; } @@ -84,6 +91,8 @@ bool PluginManager::isEnabled( const std::string& id ) const { } bool PluginManager::reload( const std::string& id ) { + if ( mClosing ) + return false; if ( !isPluginReloadEnabled() ) { Log::warning( "PluginManager: tried to reload a plugin but plugin reload is not enabled." ); return false; @@ -383,6 +392,33 @@ void PluginManager::registerFileSystemListener( Plugin* plugin ) { mFileSystemListener->removeListener( listenerId ); } +void PluginManager::unloadPlugin( Plugin* plugin ) { + // Complete all application-facing teardown while virtual dispatch still reaches the complete + // plugin type. The destructor below may run on a worker and must only release owned resources. + unsubscribeFileSystemListener( plugin ); + { + Lock l( mSubscribedPluginsMutex ); + mSubscribedPlugins.erase( plugin->getId() ); + } + plugin->shutdown(); + if ( mClosing ) { + eeDelete( plugin ); + return; + } + { + std::lock_guard lock( mPendingUnloadsMutex ); + ++mPendingUnloads; + } + mThreadPool->run( [this, plugin] { + eeDelete( plugin ); + { + std::lock_guard lock( mPendingUnloadsMutex ); + --mPendingUnloads; + } + mPendingUnloadsCondition.notify_one(); + } ); +} + void PluginManager::sendBroadcast( const PluginMessageType& notification, const PluginMessageFormat& format, void* data ) { if ( mClosing ) diff --git a/src/tools/ecode/plugins/pluginmanager.hpp b/src/tools/ecode/plugins/pluginmanager.hpp index a135a3df4..79ec63d3a 100644 --- a/src/tools/ecode/plugins/pluginmanager.hpp +++ b/src/tools/ecode/plugins/pluginmanager.hpp @@ -12,8 +12,10 @@ #include #include +#include #include #include +#include #include #include @@ -365,6 +367,8 @@ class PluginManager { bool isClosing() const; + void beginShutdown(); + PluginContextProvider* getPluginContext() const { return mPluginContext; } void forEachPlugin( std::function fn ); @@ -395,6 +399,9 @@ class PluginManager { OnLoadFileCb mLoadFileFn; UnorderedSet mPluginsFSSubs; UnorderedMap mPluginFSListenerIds; + std::mutex mPendingUnloadsMutex; + std::condition_variable mPendingUnloadsCondition; + size_t mPendingUnloads{ 0 }; bool mClosing{ false }; bool mPluginReloadEnabled{ false }; bool mPluginsDisabled{ false }; @@ -412,6 +419,8 @@ class PluginManager { void unsubscribeFileSystemListener(); void registerFileSystemListener( Plugin* plugin ); + + void unloadPlugin( Plugin* plugin ); }; class PluginsModel : public Model { diff --git a/src/tools/ecode/plugins/spellchecker/spellcheckerplugin.cpp b/src/tools/ecode/plugins/spellchecker/spellcheckerplugin.cpp index cd1c3f558..d8c227d7d 100644 --- a/src/tools/ecode/plugins/spellchecker/spellcheckerplugin.cpp +++ b/src/tools/ecode/plugins/spellchecker/spellcheckerplugin.cpp @@ -230,6 +230,7 @@ void SpellCheckerPlugin::goToPrevError( UICodeEditor* editor ) { void SpellCheckerPlugin::onUnregisterDocument( TextDocument* doc ) { mDirtyDoc.erase( doc ); + PluginBase::onUnregisterDocument( doc ); } void SpellCheckerPlugin::onDocumentChanged( UICodeEditor*, TextDocument* oldDoc ) { diff --git a/src/tools/ecode/plugins/xmltools/xmltoolsplugin.cpp b/src/tools/ecode/plugins/xmltools/xmltoolsplugin.cpp index 2c2f4be12..69e9bb1bf 100644 --- a/src/tools/ecode/plugins/xmltools/xmltoolsplugin.cpp +++ b/src/tools/ecode/plugins/xmltools/xmltoolsplugin.cpp @@ -43,11 +43,6 @@ XMLToolsPlugin::XMLToolsPlugin( PluginManager* pluginManager, bool sync ) : XMLToolsPlugin::~XMLToolsPlugin() { waitUntilLoaded(); mShuttingDown = true; - { - Lock l( mClientsMutex ); - for ( const auto& client : mClients ) - client.first->unregisterClient( client.second.get() ); - } } bool XMLToolsPlugin::getHighlightMatch() const { @@ -122,8 +117,20 @@ void XMLToolsPlugin::onRegisterDocument( TextDocument* doc ) { void XMLToolsPlugin::onUnregisterDocument( TextDocument* doc ) { Lock l( mClientsMutex ); - doc->unregisterClient( mClients[doc].get() ); - mClients.erase( doc ); + auto client = mClients.find( doc ); + if ( client != mClients.end() ) { + doc->unregisterClient( client->second.get() ); + mClients.erase( client ); + } + PluginBase::onUnregisterDocument( doc ); +} + +void XMLToolsPlugin::unregisterEditors() { + PluginBase::unregisterEditors(); + Lock l( mClientsMutex ); + for ( const auto& client : mClients ) + client.first->unregisterClient( client.second.get() ); + mClients.clear(); } bool XMLToolsPlugin::isOverMatch( TextDocument* doc, const Int64& index ) const { diff --git a/src/tools/ecode/plugins/xmltools/xmltoolsplugin.hpp b/src/tools/ecode/plugins/xmltools/xmltoolsplugin.hpp index 0f951963b..6f3a33ad1 100644 --- a/src/tools/ecode/plugins/xmltools/xmltoolsplugin.hpp +++ b/src/tools/ecode/plugins/xmltools/xmltoolsplugin.hpp @@ -131,6 +131,8 @@ class XMLToolsPlugin : public PluginBase { virtual void onUnregisterDocument( TextDocument* doc ) override; + void unregisterEditors() override; + bool isOverMatch( TextDocument* doc, const Int64& index ) const; bool isVisibleInRange( TextDocument* doc, const DocumentLineRange& docLineRange );