diff --git a/include/eepp/system/sys.hpp b/include/eepp/system/sys.hpp index 5224959fb..6cfccd906 100644 --- a/include/eepp/system/sys.hpp +++ b/include/eepp/system/sys.hpp @@ -35,9 +35,12 @@ class EE_API Sys { /** Wait the time defined before returning. */ static void sleep( const Time& time ); - /** @return The application path ( the executable path ) */ + /** @return The application path ( the executable path without the executable ) */ static std::string getProcessPath(); + /** @return The process path ( the executable file path ) */ + static std::string getProcessFilePath(); + /** @return The System Time */ static double getSystemTime(); @@ -94,6 +97,9 @@ class EE_API Sys { * Other platforms will do nothing. */ static bool windowAttachConsole(); + + /** Executes a command */ + static void execute( const std::string& cmd ); }; }} // namespace EE::System diff --git a/include/eepp/ui/doc/textdocument.hpp b/include/eepp/ui/doc/textdocument.hpp index 672f92bc4..0cf94810f 100644 --- a/include/eepp/ui/doc/textdocument.hpp +++ b/include/eepp/ui/doc/textdocument.hpp @@ -361,28 +361,26 @@ class EE_API TextDocument { bool removeCommand( const std::string& command ); TextRange find( const String& text, TextPosition from = { 0, 0 }, bool caseSensitive = true, - bool wholeWord = false, const FindReplaceType& type = FindReplaceType::Normal, + bool wholeWord = false, FindReplaceType type = FindReplaceType::Normal, TextRange restrictRange = TextRange() ); TextRange findLast( const String& text, TextPosition from = { 0, 0 }, bool caseSensitive = true, - bool wholeWord = false, - const FindReplaceType& type = FindReplaceType::Normal, + bool wholeWord = false, FindReplaceType type = FindReplaceType::Normal, TextRange restrictRange = TextRange() ); TextRanges findAll( const String& text, bool caseSensitive = true, bool wholeWord = false, - const FindReplaceType& type = FindReplaceType::Normal, + FindReplaceType type = FindReplaceType::Normal, TextRange restrictRange = TextRange(), size_t maxResults = 0 ); int replaceAll( const String& text, const String& replace, const bool& caseSensitive = true, - const bool& wholeWord = false, - const FindReplaceType& type = FindReplaceType::Normal, + const bool& wholeWord = false, FindReplaceType type = FindReplaceType::Normal, TextRange restrictRange = TextRange() ); TextPosition replaceSelection( const String& replace ); TextPosition replace( String search, const String& replace, TextPosition from = { 0, 0 }, const bool& caseSensitive = true, const bool& wholeWord = false, - const FindReplaceType& type = FindReplaceType::Normal, + FindReplaceType type = FindReplaceType::Normal, TextRange restrictRange = TextRange() ); String getIndentString(); @@ -699,13 +697,11 @@ class EE_API TextDocument { LoadStatus loadFromStream( IOStream& file, std::string path, bool callReset ); TextRange findText( String text, TextPosition from = { 0, 0 }, bool caseSensitive = true, - bool wholeWord = false, - const FindReplaceType& type = FindReplaceType::Normal, + bool wholeWord = false, FindReplaceType type = FindReplaceType::Normal, TextRange restrictRange = TextRange() ); TextRange findTextLast( String text, TextPosition from = { 0, 0 }, bool caseSensitive = true, - bool wholeWord = false, - const FindReplaceType& type = FindReplaceType::Normal, + bool wholeWord = false, FindReplaceType type = FindReplaceType::Normal, TextRange restrictRange = TextRange() ); }; diff --git a/src/eepp/system/sys.cpp b/src/eepp/system/sys.cpp index 445726671..d93922caa 100644 --- a/src/eepp/system/sys.cpp +++ b/src/eepp/system/sys.cpp @@ -20,9 +20,9 @@ #endif #if EE_PLATFORM == EE_PLATFORM_MACOS +#include #include #include -#include #elif EE_PLATFORM == EE_PLATFORM_WIN #ifndef NOMINMAX #define NOMINMAX @@ -48,6 +48,10 @@ #include #endif +#if EE_PLATFORM == EE_PLATFORM_MACOS || EE_PLATFORM == EE_PLATFORM_IOS +#include +#endif + #if EE_PLATFORM == EE_PLATFORM_MACOS || EE_PLATFORM == EE_PLATFORM_BSD || \ EE_PLATFORM == EE_PLATFORM_IOS #include @@ -57,6 +61,7 @@ #if EE_PLATFORM == EE_PLATFORM_WIN #include #include +#include #else #include #endif @@ -488,7 +493,7 @@ static std::string sGetProcessPath() { #if EE_PLATFORM == EE_PLATFORM_MACOS char pathbuf[PROC_PIDPATHINFO_MAXSIZE]; pid_t pid = getpid(); - int ret = proc_pidpath (pid, pathbuf, sizeof(pathbuf)); + int ret = proc_pidpath( pid, pathbuf, sizeof( pathbuf ) ); if ( ret >= 0 ) return FileSystem::fileRemoveFileName( std::string( pathbuf ) ); @@ -1143,4 +1148,61 @@ bool Sys::windowAttachConsole() { return true; } +#if EE_PLATFORM == EE_PLATFORM_WIN +static void windowsSystem( const std::string& programPath ) { + STARTUPINFO si; + PROCESS_INFORMATION pi; + ZeroMemory( &si, sizeof( si ) ); + si.cb = sizeof( si ); + ZeroMemory( &pi, sizeof( pi ) ); + + if ( CreateProcess( NULL, const_cast( programPath.c_str() ), NULL, NULL, FALSE, 0, NULL, + NULL, &si, &pi ) ) { + CloseHandle( pi.hProcess ); + CloseHandle( pi.hThread ); + } +} +#endif + +void Sys::execute( const std::string& cmd ) { +#if EE_PLATFORM == EE_PLATFORM_WIN + windowsSystem( cmd ); +#else + std::system( cmd.c_str() ); +#endif +} + +std::string Sys::getProcessFilePath() { + char exename[PATH_MAX]; + +#if EE_PLATFORM == EE_PLATFORM_WIN + int len = GetModuleFileName( NULL, exename, PATH_MAX - 1 ); + exename[len] = '\0'; +#elif EE_PLATFORM == EE_PLATFORM_LINUX || EE_PLATFORM == EE_PLATFORM_ANDROID + char path[] = "/proc/self/exe"; + ssize_t len = readlink( path, exename, PATH_MAX - 1 ); + if ( len > 0 ) + exename[len] = '\0'; +#elif EE_PLATFORM == EE_PLATFORM_MACOS || EE_PLATFORM == EE_PLATFORM_IOS + /* use realpath to resolve a symlink if the process was launched from one. + ** This happens when Homebrew installs a cack and creates a symlink in + ** /usr/loca/bin for launching the executable from the command line. */ + unsigned size = PATH_MAX; + char exepath[size]; + _NSGetExecutablePath( exepath, &size ); + realpath( exepath, exename ); +#elif EE_PLATFORM == EE_PLATFORM_BSD + size_t len = PATH_MAX; + const int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1 }; + sysctl( mib, 4, exename, &len, NULL, 0 ); +#elif EE_PLATFORM == EE_PLATFORM_HAIKU + image_info info; + get_image_info( 0, &info ); + strncpy( exename, info.name, sizeof( exename ) ); +#else + *exename = 0; +#endif + + return std::string( exename ); +} }} // namespace EE::System diff --git a/src/eepp/ui/doc/textdocument.cpp b/src/eepp/ui/doc/textdocument.cpp index 09ce8597a..f701a3a0c 100644 --- a/src/eepp/ui/doc/textdocument.cpp +++ b/src/eepp/ui/doc/textdocument.cpp @@ -2160,8 +2160,7 @@ static std::pair findLastType( const String& str, const String& } TextRange TextDocument::findText( String text, TextPosition from, bool caseSensitive, - bool wholeWord, const FindReplaceType& type, - TextRange restrictRange ) { + bool wholeWord, FindReplaceType type, TextRange restrictRange ) { if ( text.empty() ) return TextRange(); from = sanitizePosition( from ); @@ -2213,7 +2212,7 @@ TextRange TextDocument::findText( String text, TextPosition from, bool caseSensi } TextRange TextDocument::findTextLast( String text, TextPosition from, bool caseSensitive, - bool wholeWord, const FindReplaceType& type, + bool wholeWord, FindReplaceType type, TextRange restrictRange ) { if ( text.empty() ) return TextRange(); @@ -2268,8 +2267,7 @@ TextRange TextDocument::findTextLast( String text, TextPosition from, bool caseS } TextRange TextDocument::find( const String& text, TextPosition from, bool caseSensitive, - bool wholeWord, const FindReplaceType& type, - TextRange restrictRange ) { + bool wholeWord, FindReplaceType type, TextRange restrictRange ) { std::vector textLines = text.split( '\n', true, true ); if ( textLines.empty() || textLines.size() > mLines.size() ) @@ -2349,8 +2347,7 @@ TextRange TextDocument::find( const String& text, TextPosition from, bool caseSe } TextRange TextDocument::findLast( const String& text, TextPosition from, bool caseSensitive, - bool wholeWord, const FindReplaceType& type, - TextRange restrictRange ) { + bool wholeWord, FindReplaceType type, TextRange restrictRange ) { std::vector textLines = text.split( '\n', true, true ); if ( textLines.empty() || textLines.size() > mLines.size() ) @@ -2439,7 +2436,7 @@ bool TextDocument::isInsertingText() const { } TextRanges TextDocument::findAll( const String& text, bool caseSensitive, bool wholeWord, - const FindReplaceType& type, TextRange restrictRange, + FindReplaceType type, TextRange restrictRange, size_t maxResults ) { TextRanges all; TextRange found; @@ -2475,7 +2472,7 @@ TextRanges TextDocument::findAll( const String& text, bool caseSensitive, bool w } int TextDocument::replaceAll( const String& text, const String& replace, const bool& caseSensitive, - const bool& wholeWord, const FindReplaceType& type, + const bool& wholeWord, FindReplaceType type, TextRange restrictRange ) { if ( text.empty() ) return 0; @@ -2537,7 +2534,7 @@ void TextDocument::selectAllMatches() { TextPosition TextDocument::replace( String search, const String& replace, TextPosition from, const bool& caseSensitive, const bool& wholeWord, - const FindReplaceType& type, TextRange restrictRange ) { + FindReplaceType type, TextRange restrictRange ) { TextRange found( findText( search, from, caseSensitive, wholeWord, type, restrictRange ) ); if ( found.isValid() ) { setSelection( found ); diff --git a/src/tools/ecode/docsearchcontroller.cpp b/src/tools/ecode/docsearchcontroller.cpp index 4dc5f694a..6956e1ed9 100644 --- a/src/tools/ecode/docsearchcontroller.cpp +++ b/src/tools/ecode/docsearchcontroller.cpp @@ -6,17 +6,18 @@ namespace ecode { DocSearchController::DocSearchController( UICodeEditorSplitter* editorSplitter, App* app ) : mSplitter( editorSplitter ), mApp( app ) {} +DocSearchController::~DocSearchController() { + Clock clock; + while ( mSearchingCount && clock.getElapsedTime() < Seconds( 1 ) ) + Sys::sleep( Milliseconds( 10 ) ); +} + void DocSearchController::refreshHighlight() { if ( mSearchState.editor && mSplitter->editorExists( mSearchState.editor ) ) { mSearchState.text = mFindInput->getText(); mSearchState.editor->setHighlightWord( mSearchState.toTextSearchParams() ); if ( !mSearchState.text.empty() ) { - mSearchState.editor->getDocument().setSelection( { 0, 0 } ); - if ( !findNextText( mSearchState ) ) { - mFindInput->addClass( "error" ); - } else { - mFindInput->removeClass( "error" ); - } + findNextText( mSearchState, true ); } else { mFindInput->removeClass( "error" ); mSearchState.editor->getDocument().setSelection( @@ -201,86 +202,113 @@ void DocSearchController::showFindView() { editor->getDocument().setActiveClient( editor ); } -bool DocSearchController::findPrevText( SearchState& search ) { +void DocSearchController::findPrevText( SearchState& search ) { if ( search.text.empty() ) search.text = mLastSearch; if ( !search.editor || !mSplitter->editorExists( search.editor ) || search.text.empty() ) - return false; + return; search.editor->getDocument().setActiveClient( search.editor ); mLastSearch = search.text; - TextDocument& doc = search.editor->getDocument(); - TextRange range = doc.getDocRange(); - TextPosition from = doc.getSelection( true ).start(); - if ( search.range.isValid() ) { - range = doc.sanitizeRange( search.range ).normalized(); - from = from < range.start() ? range.start() : from; - } - String txt( search.text ); - if ( search.escapeSequences ) - txt.unescape(); + Uint64 tag = String::hash( "DocSearchController::findPrevText-" + + search.editor->getDocument().getHashHexString() ); + mApp->getThreadPool()->removeWithTag( tag ); + mApp->getThreadPool()->run( [this, search] { + ScopedOp op( [this] { mSearchingCount++; }, [this] { mSearchingCount--; } ); + TextDocument& doc = search.editor->getDocument(); + TextRange range = doc.getDocRange(); + TextPosition from = doc.getSelection( true ).start(); + if ( search.range.isValid() ) { + range = doc.sanitizeRange( search.range ).normalized(); + from = from < range.start() ? range.start() : from; + } - TextRange found = doc.findLast( txt, from, search.caseSensitive, search.wholeWord, search.type, - search.range ); - if ( found.isValid() ) { - doc.setSelection( found ); - mSplitter->addEditorPositionToNavigationHistory( search.editor ); - mFindInput->removeClass( "error" ); - return true; - } else { - found = doc.findLast( txt, range.end(), search.caseSensitive, search.wholeWord, search.type, - range ); + String txt( search.text ); + if ( search.escapeSequences ) + txt.unescape(); + + TextRange found = doc.findLast( txt, from, search.caseSensitive, search.wholeWord, + search.type, search.range ); if ( found.isValid() ) { doc.setSelection( found ); - mSplitter->addEditorPositionToNavigationHistory( search.editor ); - mFindInput->removeClass( "error" ); - return true; + mFindInput->runOnMainThread( [this, search] { + mSplitter->addEditorPositionToNavigationHistory( search.editor ); + mFindInput->removeClass( "error" ); + } ); + return; + } else { + found = doc.findLast( txt, range.end(), search.caseSensitive, search.wholeWord, + search.type, range ); + if ( found.isValid() ) { + doc.setSelection( found ); + mFindInput->runOnMainThread( [this, search] { + mSplitter->addEditorPositionToNavigationHistory( search.editor ); + mFindInput->removeClass( "error" ); + } ); + return; + } } - } - mFindInput->addClass( "error" ); - return false; + mFindInput->runOnMainThread( [this] { mFindInput->addClass( "error" ); } ); + } ); } -bool DocSearchController::findNextText( SearchState& search ) { +void DocSearchController::findNextText( SearchState& search, bool resetSelection ) { if ( search.text.empty() ) search.text = mLastSearch; if ( !search.editor || !mSplitter->editorExists( search.editor ) || search.text.empty() ) - return false; + return; search.editor->getDocument().setActiveClient( search.editor ); mLastSearch = search.text; - TextDocument& doc = search.editor->getDocument(); - TextRange range = doc.getDocRange(); - TextPosition from = doc.getSelection( true ).end(); - if ( search.range.isValid() ) { - range = doc.sanitizeRange( search.range ).normalized(); - from = from < range.start() ? range.start() : from; - } - String txt( search.text ); - if ( search.escapeSequences ) - txt.unescape(); + Uint64 tag = String::hash( "DocSearchController::findNextText-" + + search.editor->getDocument().getHashHexString() ); - TextRange found = - doc.find( txt, from, search.caseSensitive, search.wholeWord, search.type, range ); - if ( found.isValid() ) { - doc.setSelection( found.reversed() ); - mSplitter->addEditorPositionToNavigationHistory( search.editor ); - mFindInput->removeClass( "error" ); - return true; - } else { - found = doc.find( txt, range.start(), search.caseSensitive, search.wholeWord, search.type, - range ); - if ( found.isValid() ) { - doc.setSelection( found.reversed() ); - mSplitter->addEditorPositionToNavigationHistory( search.editor ); - mFindInput->removeClass( "error" ); - return true; - } - } - mFindInput->addClass( "error" ); - return false; + mApp->getThreadPool()->removeWithTag( tag ); + mApp->getThreadPool()->run( + [this, search, resetSelection] { + ScopedOp op( [this] { mSearchingCount++; }, [this] { mSearchingCount--; } ); + TextDocument& doc = search.editor->getDocument(); + TextRange range = doc.getDocRange(); + TextPosition from = + resetSelection ? TextPosition( 0, 0 ) : doc.getSelection( true ).end(); + + if ( search.range.isValid() ) { + range = doc.sanitizeRange( search.range ).normalized(); + from = from < range.start() ? range.start() : from; + } + + String txt( search.text ); + if ( search.escapeSequences ) + txt.unescape(); + + TextRange found = + doc.find( txt, from, search.caseSensitive, search.wholeWord, search.type, range ); + + if ( found.isValid() ) { + doc.setSelection( found.reversed() ); + mFindInput->runOnMainThread( [this, search] { + mSplitter->addEditorPositionToNavigationHistory( search.editor ); + mFindInput->removeClass( "error" ); + } ); + return; + } else { + found = doc.find( txt, range.start(), search.caseSensitive, search.wholeWord, + search.type, range ); + if ( found.isValid() ) { + doc.setSelection( found.reversed() ); + mFindInput->runOnMainThread( [this, search] { + mSplitter->addEditorPositionToNavigationHistory( search.editor ); + mFindInput->removeClass( "error" ); + } ); + return; + } + } + + mFindInput->runOnMainThread( [this] { mFindInput->addClass( "error" ); } ); + }, + {}, tag ); } bool DocSearchController::replaceSelection( SearchState& search, const String& replacement ) { @@ -333,13 +361,13 @@ int DocSearchController::replaceAll( SearchState& search, const String& replace return count; } -bool DocSearchController::findAndReplace( SearchState& search, const String& replace ) { +void DocSearchController::findAndReplace( SearchState& search, const String& replace ) { if ( !search.editor || !mSplitter->editorExists( search.editor ) ) - return false; + return; if ( search.text.empty() ) search.text = mLastSearch; if ( search.text.empty() ) - return false; + return; search.editor->getDocument().setActiveClient( search.editor ); mLastSearch = search.text; TextDocument& doc = search.editor->getDocument(); @@ -352,9 +380,9 @@ bool DocSearchController::findAndReplace( SearchState& search, const String& rep } if ( doc.hasSelection() && doc.getSelectedText() == txt ) { - return replaceSelection( search, repl ); + replaceSelection( search, repl ); } else { - return findNextText( search ); + findNextText( search ); } } diff --git a/src/tools/ecode/docsearchcontroller.hpp b/src/tools/ecode/docsearchcontroller.hpp index 18a1b2059..6bd9fbfa8 100644 --- a/src/tools/ecode/docsearchcontroller.hpp +++ b/src/tools/ecode/docsearchcontroller.hpp @@ -46,21 +46,23 @@ class DocSearchController { DocSearchController( UICodeEditorSplitter*, App* app ); + ~DocSearchController(); + void initSearchBar( UISearchBar* searchBar, const SearchBarConfig& searchBarConfig, std::unordered_map keybindings = getDefaultKeybindings() ); void showFindView(); - bool findPrevText( SearchState& search ); + void findPrevText( SearchState& search ); - bool findNextText( SearchState& search ); + void findNextText( SearchState& search, bool resetSelection = false ); bool replaceSelection( SearchState& search, const String& replacement ); int replaceAll( SearchState& search, const String& replace ); - bool findAndReplace( SearchState& search, const String& replace ); + void findAndReplace( SearchState& search, const String& replace ); void hideSearchBar(); @@ -73,6 +75,7 @@ class DocSearchController { void selectAll( SearchState& search ); void refreshHighlight(); + protected: UICodeEditorSplitter* mSplitter{ nullptr }; UITextInput* mFindInput{ nullptr }; @@ -85,6 +88,7 @@ class DocSearchController { App* mApp{ nullptr }; SearchState mSearchState; String mLastSearch; + size_t mSearchingCount{ 0 }; }; } // namespace ecode diff --git a/src/tools/ecode/ecode.cpp b/src/tools/ecode/ecode.cpp index fe317d44a..abbf6bd06 100644 --- a/src/tools/ecode/ecode.cpp +++ b/src/tools/ecode/ecode.cpp @@ -552,6 +552,7 @@ void App::onReady() { // Plugin reload is only available right after we render the first frame and the editor is ready // to run. mPluginManager->setPluginReloadEnabled( true ); + Log::info( "App Ready" ); } void App::mainLoop() { @@ -1875,6 +1876,7 @@ std::map App::getLocalKeybindings() { "open-workspace-symbol-search" }, { { KEY_P, KeyMod::getDefaultModifier() | KEYMOD_SHIFT }, "open-document-symbol-search" }, + { { KEY_N, KEYMOD_SHIFT | KEYMOD_LALT }, "create-new-window" }, }; } @@ -1950,7 +1952,8 @@ std::vector App::getUnlockedCommands() { "open-workspace-symbol-search", "open-document-symbol-search", "show-folder-treeview-tab", - "show-build-tab" }; + "show-build-tab", + "create-new-window" }; } bool App::isUnlockedCommand( const std::string& command ) { diff --git a/src/tools/ecode/ecode.hpp b/src/tools/ecode/ecode.hpp index f2a5e991d..2c2d6dcd3 100644 --- a/src/tools/ecode/ecode.hpp +++ b/src/tools/ecode/ecode.hpp @@ -293,6 +293,13 @@ class App : public UICodeEditorSplitter::Client { mTerminalManager->configureTerminalScrollback(); } ); t.setCommand( "check-for-updates", [this] { checkForUpdates( false ); } ); + t.setCommand( "create-new-window", [this] { + std::string processPath = Sys::getProcessFilePath(); + if ( !processPath.empty() ) { + std::string cmd( processPath + " -x" ); + Sys::execute( cmd ); + } + } ); mSplitter->registerSplitterCommands( t ); } diff --git a/src/tools/ecode/iconmanager.cpp b/src/tools/ecode/iconmanager.cpp index 723a4240a..55fcf01d7 100644 --- a/src/tools/ecode/iconmanager.cpp +++ b/src/tools/ecode/iconmanager.cpp @@ -73,7 +73,8 @@ void IconManager::init( UISceneNode* sceneNode, FontTrueType* iconFont, FontTrue { "add", 0xea12 }, { "hammer", 0xedee }, { "eraser", 0xec9e }, - { "file-search", 0xed05 } }; + { "file-search", 0xed05 }, + { "window", 0xf2c4 } }; for ( const auto& icon : icons ) iconTheme->add( UIGlyphIcon::New( icon.first, iconFont, icon.second ) ); diff --git a/src/tools/ecode/plugins/formatter/formatterplugin.cpp b/src/tools/ecode/plugins/formatter/formatterplugin.cpp index 954a6ac0f..2e37f8abd 100644 --- a/src/tools/ecode/plugins/formatter/formatterplugin.cpp +++ b/src/tools/ecode/plugins/formatter/formatterplugin.cpp @@ -203,9 +203,11 @@ void FormatterPlugin::loadFormatterConfig( const std::string& path, bool updateC j["keybindings"]["format-doc"] = mKeyBindings["format-doc"]; if ( updateConfigFile ) { - data = j.dump( 2 ); - FileSystem::fileWrite( path, data ); - mConfigHash = String::hash( data ); + std::string newData = j.dump( 2 ); + if ( newData != data ) { + FileSystem::fileWrite( path, newData ); + mConfigHash = String::hash( data ); + } } if ( !j.contains( "formatters" ) ) diff --git a/src/tools/ecode/plugins/linter/linterplugin.cpp b/src/tools/ecode/plugins/linter/linterplugin.cpp index 1d94679b9..d84364333 100644 --- a/src/tools/ecode/plugins/linter/linterplugin.cpp +++ b/src/tools/ecode/plugins/linter/linterplugin.cpp @@ -183,7 +183,11 @@ void LinterPlugin::loadLinterConfig( const std::string& path, bool updateConfigF } if ( updateConfigFile ) { - FileSystem::fileWrite( path, j.dump( 2 ) ); + std::string newData( j.dump( 2 ) ); + if ( newData != data ) { + FileSystem::fileWrite( path, newData ); + mConfigHash = String::hash( newData ); + } } if ( !j.contains( "linters" ) ) diff --git a/src/tools/ecode/plugins/lsp/lspclientplugin.cpp b/src/tools/ecode/plugins/lsp/lspclientplugin.cpp index 95e45249f..8b674433b 100644 --- a/src/tools/ecode/plugins/lsp/lspclientplugin.cpp +++ b/src/tools/ecode/plugins/lsp/lspclientplugin.cpp @@ -908,7 +908,11 @@ void LSPClientPlugin::loadLSPConfig( std::vector& lsps, const std } if ( updateConfigFile ) { - FileSystem::fileWrite( path, j.dump( 2 ) ); + std::string newData( j.dump( 2 ) ); + if ( newData != data ) { + FileSystem::fileWrite( path, newData ); + mConfigHash = String::hash( newData ); + } } if ( !j.contains( "servers" ) ) diff --git a/src/tools/ecode/plugins/pluginmanager.cpp b/src/tools/ecode/plugins/pluginmanager.cpp index 4303d00d6..0eddace5f 100644 --- a/src/tools/ecode/plugins/pluginmanager.cpp +++ b/src/tools/ecode/plugins/pluginmanager.cpp @@ -22,6 +22,7 @@ PluginManager::~PluginManager() { Log::debug( "PluginManager: unloading plugin %s", plugin.second->getTitle().c_str() ); eeDelete( plugin.second ); } + unsubscribeFileSystemListener(); } void PluginManager::registerPlugin( const PluginDefinition& def ) { @@ -65,12 +66,18 @@ bool PluginManager::isEnabled( const std::string& id ) const { } bool PluginManager::reload( const std::string& id ) { + if ( !isPluginReloadEnabled() ) { + Log::warning( "PluginManager: tried to reload a plugin but plugin reload is not enabled." ); + return false; + } if ( isEnabled( id ) ) { - Log::warning( "PluginManager: reloading plugin %s", id.c_str() ); + Log::warning( "PluginManager: reloading plugin %s from process %u", id.c_str(), + Sys::getProcessID() ); setEnabled( id, false ); setEnabled( id, true ); return true; } + Log::warning( "PluginManager: tried to reload a plugin but plugin is not enabled." ); return false; } @@ -254,6 +261,31 @@ void PluginManager::setFileSystemListener( FileSystemListener* listener ) { mFileSystemListener = listener; sendBroadcast( PluginMessageType::FileSystemListenerReady, PluginMessageFormat::Empty, nullptr ); + subscribeFileSystemListener(); +} + +void PluginManager::subscribeFileSystemListener( Plugin* plugin ) { + mPluginsFSSubs.insert( plugin ); +} + +void PluginManager::unsubscribeFileSystemListener( Plugin* plugin ) { + mPluginsFSSubs.erase( plugin ); +} + +void PluginManager::subscribeFileSystemListener() { + if ( mFileSystemListenerCb != 0 || mFileSystemListener == nullptr ) + return; + + mFileSystemListenerCb = + mFileSystemListener->addListener( [this]( const FileEvent& ev, const FileInfo& file ) { + for ( Plugin* plugin : mPluginsFSSubs ) + plugin->onFileSystemEvent( ev, file ); + } ); +} + +void PluginManager::unsubscribeFileSystemListener() { + if ( mFileSystemListenerCb != 0 && mFileSystemListener ) + mFileSystemListener->removeListener( mFileSystemListenerCb ); } void PluginManager::sendBroadcast( const PluginMessageType& notification, @@ -456,45 +488,6 @@ Plugin::Plugin( PluginManager* manager ) : // avoid concurrency issues {} -void Plugin::subscribeFileSystemListener() { - if ( mFileSystemListenerCb != 0 || mManager->getFileSystemListener() == nullptr ) - return; - - mConfigFileInfo = FileInfo( mConfigPath ); - - mFileSystemListenerCb = mManager->getFileSystemListener()->addListener( - [this]( const FileEvent& ev, const FileInfo& file ) { - if ( ev.type != FileSystemEventType::Modified ) - return; - if ( !mShuttingDown && !isLoading() && file.getFilepath() == mConfigPath && - file.getModificationTime() != mConfigFileInfo.getModificationTime() ) { - std::string fileContents; - FileSystem::fileGet( file.getFilepath(), fileContents ); - if ( getConfigFileHash() != String::hash( fileContents ) ) { - if ( mManager->isPluginReloadEnabled() && !isLoading() && isReady() ) { - mConfigFileInfo = file; - mManager->getFileSystemListener()->removeListener( mFileSystemListenerCb ); - mFileSystemListenerCb = 0; - mManager->reload( getId() ); - } else { - Log::debug( "Plugin %s: Configuration file has been modified: %s. But " - "plugin reload is not enabled.", - getTitle().c_str(), mConfigPath.c_str() ); - } - } else { - Log::debug( "Plugin %s: Configuration file has been modified: %s. But contents " - "are the same.", - getTitle().c_str(), mConfigPath.c_str() ); - } - } - } ); -} - -void Plugin::unsubscribeFileSystemListener() { - if ( mFileSystemListenerCb != 0 && mManager->getFileSystemListener() ) - mManager->getFileSystemListener()->removeListener( mFileSystemListenerCb ); -} - bool Plugin::isReady() const { return mReady; } @@ -511,6 +504,15 @@ bool Plugin::hasFileConfig() { return !mConfigPath.empty(); } +void Plugin::subscribeFileSystemListener() { + mConfigFileInfo = FileInfo( mConfigPath ); + mManager->subscribeFileSystemListener( this ); +} + +void Plugin::unsubscribeFileSystemListener() { + mManager->unsubscribeFileSystemListener( this ); +} + std::string Plugin::getFileConfigPath() { return mConfigPath; } @@ -519,9 +521,38 @@ PluginManager* Plugin::getManager() const { return mManager; } +void Plugin::onFileSystemEvent( const FileEvent& ev, const FileInfo& file ) { + if ( ev.type != FileSystemEventType::Modified || mShuttingDown || isLoading() ) + return; + + if ( file.getFilepath() != mConfigPath || + file.getModificationTime() == mConfigFileInfo.getModificationTime() ) + return; + + std::string fileContents; + FileSystem::fileGet( file.getFilepath(), fileContents ); + if ( getConfigFileHash() != String::hash( fileContents ) ) { + if ( mManager->isPluginReloadEnabled() && !isLoading() && isReady() ) { + mConfigFileInfo = file; + unsubscribeFileSystemListener(); + mManager->reload( getId() ); + } else { + Log::debug( "Plugin %s: Configuration file has been modified: %s. But " + "plugin reload is not enabled.", + getTitle().c_str(), mConfigPath.c_str() ); + } + } else { + Log::debug( "Plugin %s: Configuration file has been modified: %s. But contents " + "are the same.", + getTitle().c_str(), mConfigPath.c_str() ); + } +} + void Plugin::setReady() { - if ( mReady ) - Log::info( "Plugin: %s loaded and ready.", getTitle().c_str() ); + if ( mReady ) { + Log::info( "Plugin: %s loaded and ready from process %u", getTitle().c_str(), + Sys::getProcessID() ); + } } PluginBase::~PluginBase() { diff --git a/src/tools/ecode/plugins/pluginmanager.hpp b/src/tools/ecode/plugins/pluginmanager.hpp index 33d2cb0c8..69fb8cae5 100644 --- a/src/tools/ecode/plugins/pluginmanager.hpp +++ b/src/tools/ecode/plugins/pluginmanager.hpp @@ -23,6 +23,7 @@ using namespace EE::UI::Tools; namespace ecode { class PluginManager; +class Plugin; class FileSystemListener; typedef std::function PluginCreatorFn; @@ -332,6 +333,10 @@ class PluginManager { void setPluginReloadEnabled( bool pluginReloadEnabled ); + void subscribeFileSystemListener( Plugin* plugin ); + + void unsubscribeFileSystemListener( Plugin* plugin ); + protected: using SubscribedPlugins = std::map>; @@ -348,6 +353,8 @@ class PluginManager { Mutex mSubscribedPluginsMutex; SubscribedPlugins mSubscribedPlugins; OnLoadFileCb mLoadFileFn; + Uint64 mFileSystemListenerCb{ 0 }; + UnorderedSet mPluginsFSSubs; bool mClosing{ false }; bool mPluginReloadEnabled{ false }; @@ -356,6 +363,10 @@ class PluginManager { void setSplitter( UICodeEditorSplitter* splitter ); void setFileSystemListener( FileSystemListener* listener ); + + void subscribeFileSystemListener(); + + void unsubscribeFileSystemListener(); }; class PluginsModel : public Model { @@ -418,10 +429,11 @@ class Plugin : public UICodeEditorPlugin { virtual String::HashType getConfigFileHash() { return 0; } + virtual void onFileSystemEvent( const FileEvent& ev, const FileInfo& file ); + protected: PluginManager* mManager{ nullptr }; std::shared_ptr mThreadPool; - Uint64 mFileSystemListenerCb{ 0 }; std::string mConfigPath; FileInfo mConfigFileInfo; diff --git a/src/tools/ecode/plugins/xmltools/xmltoolsplugin.cpp b/src/tools/ecode/plugins/xmltools/xmltoolsplugin.cpp index 4e11c611a..6cf7f989a 100644 --- a/src/tools/ecode/plugins/xmltools/xmltoolsplugin.cpp +++ b/src/tools/ecode/plugins/xmltools/xmltoolsplugin.cpp @@ -93,9 +93,11 @@ void XMLToolsPlugin::load( PluginManager* pluginManager ) { } if ( updateConfigFile ) { - data = j.dump( 2 ); - FileSystem::fileWrite( path, data ); - mConfigHash = String::hash( data ); + std::string newData = j.dump( 2 ); + if ( newData != data ) { + FileSystem::fileWrite( path, newData ); + mConfigHash = String::hash( newData ); + } } subscribeFileSystemListener(); diff --git a/src/tools/ecode/settingsmenu.cpp b/src/tools/ecode/settingsmenu.cpp index 65de23018..d0d894ae7 100644 --- a/src/tools/ecode/settingsmenu.cpp +++ b/src/tools/ecode/settingsmenu.cpp @@ -36,6 +36,10 @@ void SettingsMenu::createSettingsMenu( App* app ) { ->add( i18n( "new_terminal", "New Terminal" ), findIcon( "terminal" ), getKeybind( "create-new-terminal" ) ) ->setId( "create-new-terminal" ); + mSettingsMenu + ->add( i18n( "new_window", "New Window" ), findIcon( "window" ), + getKeybind( "create-new-window" ) ) + ->setId( "create-new-window" ); mSettingsMenu ->add( i18n( "open_file", "Open File..." ), findIcon( "document-open" ), getKeybind( "open-file" ) )