diff --git a/include/eepp/ui/doc/textdocument.hpp b/include/eepp/ui/doc/textdocument.hpp index 6663d0675..6af48edac 100644 --- a/include/eepp/ui/doc/textdocument.hpp +++ b/include/eepp/ui/doc/textdocument.hpp @@ -567,9 +567,12 @@ class EE_API TextDocument { void setHAsCpp( bool hAsCpp ); + const Uint64& getModificationId() const; + protected: friend class UndoStack; + Uint64 mModificationId{ 0 }; UndoStack mUndoStack; std::string mFilePath; std::string mLoadingFilePath; diff --git a/src/eepp/core/string.cpp b/src/eepp/core/string.cpp index e248385c0..4edb1be8e 100644 --- a/src/eepp/core/string.cpp +++ b/src/eepp/core/string.cpp @@ -151,11 +151,18 @@ String String::escape( const String& str ) { String String::unescape( const String& str ) { String output; + bool lastWasEscape = false; + bool lastInsertedEscape = false; + for ( size_t i = 0; i < str.size(); i++ ) { - if ( i > 0 && str[i - 1] == '\\' ) { + lastWasEscape = lastWasEscape && !lastInsertedEscape; + lastInsertedEscape = false; + + if ( lastWasEscape ) { switch ( str[i] ) { case '\\': output.push_back( '\\' ); + lastInsertedEscape = true; break; case 'r': output.push_back( '\r' ); @@ -250,13 +257,16 @@ String String::unescape( const String& str ) { break; } default: { - // Undefined behavior + output.push_back( '\\' ); + output.push_back( str[i] ); break; } } } else if ( str[i] != '\\' ) { output.push_back( str[i] ); } + + lastWasEscape = str[i] == '\\'; } return output; } diff --git a/src/eepp/ui/doc/syntaxdefinitionmanager.cpp b/src/eepp/ui/doc/syntaxdefinitionmanager.cpp index 5a6203d59..8b7021acb 100644 --- a/src/eepp/ui/doc/syntaxdefinitionmanager.cpp +++ b/src/eepp/ui/doc/syntaxdefinitionmanager.cpp @@ -441,12 +441,12 @@ static void addJavaScript() { { { { "//.-\n" }, "comment" }, { { "/%*", "%*/" }, "comment" }, - { { "/[%+%-%*%^%!%=%&%|%?%:%;%,%(%[%{%<%>%\\].*%f[/]", - "/[igmsuyd][igmsuyd]?[igmsuyd]?", "\\" }, - "string" }, { { "\"", "\"", "\\" }, "string" }, { { "'", "'", "\\" }, "string" }, { { "`", "`", "\\" }, "string" }, + { { "/[%+%-%*%^%!%=%&%|%?%:%;%,%(%[%{%<%>%\\].*%f[/]", + "/[igmsuyd\n]?[igmsuyd\n]?[igmsuyd\n]?", "\\" }, + "string" }, { { "0x[%da-fA-F]+" }, "number" }, { { "-?%d+[%d%.eE]*" }, "number" }, { { "-?%.?%d+" }, "number" }, @@ -510,12 +510,12 @@ static void addTypeScript() { { { { "//.-\n" }, "comment" }, { { "/%*", "%*/" }, "comment" }, - { { "/[%+%-%*%^%!%=%&%|%?%:%;%,%(%[%{%<%>%\\].*%f[/]", - "/[igmsuyd][igmsuyd]?[igmsuyd]?", "\\" }, - "string" }, { { "\"", "\"", "\\" }, "string" }, { { "'", "'", "\\" }, "string" }, { { "`", "`", "\\" }, "string" }, + { { "/[%+%-%*%^%!%=%&%|%?%:%;%,%(%[%{%<%>%\\].*%f[/]", + "/[igmsuyd\n]?[igmsuyd\n]?[igmsuyd\n]?", "\\" }, + "string" }, { { "0x[%da-fA-F]+" }, "number" }, { { "-?%d+[%d%.eE]*" }, "number" }, { { "-?%.?%d+" }, "number" }, diff --git a/src/eepp/ui/doc/textdocument.cpp b/src/eepp/ui/doc/textdocument.cpp index 7dbdbc8ec..4baa6f09b 100644 --- a/src/eepp/ui/doc/textdocument.cpp +++ b/src/eepp/ui/doc/textdocument.cpp @@ -879,6 +879,9 @@ TextPosition TextDocument::insert( const size_t& cursorIdx, TextPosition positio const Time& time, bool fromUndoRedo ) { if ( text.empty() ) return position; + + mModificationId++; + if ( fromUndoRedo ) { if ( cursorIdx >= mSelection.size() ) { while ( cursorIdx >= mSelection.size() ) @@ -965,6 +968,8 @@ size_t TextDocument::remove( const size_t& cursorIdx, TextRange range, if ( !range.isValid() ) return 0; + mModificationId++; + if ( fromUndoRedo ) { if ( cursorIdx >= mSelection.size() ) { while ( cursorIdx >= mSelection.size() ) @@ -1631,6 +1636,10 @@ void TextDocument::setHAsCpp( bool hAsCpp ) { mHAsCpp = hAsCpp; } +const Uint64& TextDocument::getModificationId() const { + return mModificationId; +} + void TextDocument::selectWord( bool withMulticursor ) { if ( !hasSelection() ) { setSelection( { nextWordBoundary( getSelection().start(), false ), diff --git a/src/eepp/ui/uicodeeditor.cpp b/src/eepp/ui/uicodeeditor.cpp index 8d9ec4ed0..a41f20e08 100644 --- a/src/eepp/ui/uicodeeditor.cpp +++ b/src/eepp/ui/uicodeeditor.cpp @@ -2520,14 +2520,18 @@ void UICodeEditor::updateHighlightWordCache() { [this]() { mHighlightWordProcessing = true; mHighlightWordCache = mDoc->findAll( - mHighlightWord.text, mHighlightWord.caseSensitive, mHighlightWord.wholeWord, - mHighlightWord.type, mHighlightWord.range ); + mHighlightWord.escapeSequences ? String::unescape( mHighlightWord.text ) + : mHighlightWord.text, + mHighlightWord.caseSensitive, mHighlightWord.wholeWord, mHighlightWord.type, + mHighlightWord.range ); }, [this]( const auto& ) { mHighlightWordProcessing = false; }, tag ); } else { mHighlightWordCache = - mDoc->findAll( mHighlightWord.text, mHighlightWord.caseSensitive, - mHighlightWord.wholeWord, mHighlightWord.type, mHighlightWord.range ); + mDoc->findAll( mHighlightWord.escapeSequences ? String::unescape( mHighlightWord.text ) + : mHighlightWord.text, + mHighlightWord.caseSensitive, mHighlightWord.wholeWord, + mHighlightWord.type, mHighlightWord.range ); } } @@ -2684,7 +2688,8 @@ void UICodeEditor::drawWordMatch( const String& text, const std::pair& do { pos = line.find( text, pos ); - if ( pos != String::InvalidPos ) { + const auto InvalidPos = String::InvalidPos; + if ( pos != InvalidPos ) { if ( ignoreSelectionMatch ) { TextRange selection = mDoc->getSelection( true ); if ( selection.inSameLine() && selection.start().line() == ln && diff --git a/src/tools/ecode/plugins/formatter/formatterplugin.cpp b/src/tools/ecode/plugins/formatter/formatterplugin.cpp index 1d1a78268..fafea5771 100644 --- a/src/tools/ecode/plugins/formatter/formatterplugin.cpp +++ b/src/tools/ecode/plugins/formatter/formatterplugin.cpp @@ -153,12 +153,16 @@ void FormatterPlugin::loadFormatterConfig( const std::string& path, bool updateC return; json j; + if ( updateConfigFile ) { + mConfigHash = String::hash( data ); + } + try { j = json::parse( data, nullptr, true, true ); } catch ( const json::exception& e ) { Log::error( "FormatterPlugin::loadFormatterConfig - Error parsing formatter config from " - "path %s, error: ", - path.c_str(), e.what() ); + "path %s, error: %s, config file content:\n%s", + path.c_str(), e.what(), data.c_str() ); if ( !updateConfigFile ) return; // Recreate it diff --git a/src/tools/ecode/plugins/formatter/formatterplugin.hpp b/src/tools/ecode/plugins/formatter/formatterplugin.hpp index 916b500d2..b3fce220a 100644 --- a/src/tools/ecode/plugins/formatter/formatterplugin.hpp +++ b/src/tools/ecode/plugins/formatter/formatterplugin.hpp @@ -49,6 +49,8 @@ class FormatterPlugin : public Plugin { std::string getDescription() { return Definition().description; } + virtual String::HashType getConfigFileHash() { return mConfigHash; } + void onRegister( UICodeEditor* ); void onUnregister( UICodeEditor* ); @@ -82,6 +84,7 @@ class FormatterPlugin : public Plugin { std::map mIsAutoFormatting; std::map mCapabilities; Mutex mCapabilitiesMutex; + String::HashType mConfigHash{ 0 }; bool mAutoFormatOnSave{ false }; diff --git a/src/tools/ecode/plugins/linter/linterplugin.cpp b/src/tools/ecode/plugins/linter/linterplugin.cpp index 211464533..405eca7e4 100644 --- a/src/tools/ecode/plugins/linter/linterplugin.cpp +++ b/src/tools/ecode/plugins/linter/linterplugin.cpp @@ -82,14 +82,18 @@ void LinterPlugin::loadLinterConfig( const std::string& path, bool updateConfigF j = json::parse( data, nullptr, true, true ); } catch ( const json::exception& e ) { Log::error( "LinterPlugin::loadLinterConfig - Error parsing linter config from " - "path %s, error: ", - path.c_str(), e.what() ); + "path %s, error: %s, config file content:\n%s", + path.c_str(), e.what(), data.c_str() ); if ( !updateConfigFile ) return; // Recreate it j = json::parse( "{\n\"config\":{},\n\"linters\":[]\n}\n", nullptr, true, true ); } + if ( updateConfigFile ) { + mConfigHash = String::hash( data ); + } + if ( j.contains( "config" ) ) { auto& config = j["config"]; if ( config.contains( "delay_time" ) ) diff --git a/src/tools/ecode/plugins/linter/linterplugin.hpp b/src/tools/ecode/plugins/linter/linterplugin.hpp index 3bf71b1e0..9b2739d64 100644 --- a/src/tools/ecode/plugins/linter/linterplugin.hpp +++ b/src/tools/ecode/plugins/linter/linterplugin.hpp @@ -72,6 +72,8 @@ class LinterPlugin : public Plugin { std::string getDescription() { return Definition().description; } + virtual String::HashType getConfigFileHash() { return mConfigHash; } + void onRegister( UICodeEditor* ); void onUnregister( UICodeEditor* ); @@ -123,6 +125,7 @@ class LinterPlugin : public Plugin { bool mErrorLens{ true }; std::set mLanguagesDisabled; std::set mLSPLanguagesDisabled; + String::HashType mConfigHash{ 0 }; LinterPlugin( PluginManager* pluginManager, bool sync ); diff --git a/src/tools/ecode/plugins/lsp/lspclientplugin.cpp b/src/tools/ecode/plugins/lsp/lspclientplugin.cpp index 261cdb2d9..cff189707 100644 --- a/src/tools/ecode/plugins/lsp/lspclientplugin.cpp +++ b/src/tools/ecode/plugins/lsp/lspclientplugin.cpp @@ -736,8 +736,8 @@ void LSPClientPlugin::loadLSPConfig( std::vector& lsps, const std j = json::parse( data, nullptr, true, true ); } catch ( const json::exception& e ) { Log::error( "LSPClientPlugin::loadLSPConfig - Error parsing LSP config from " - "path %s, error: ", - path.c_str(), e.what() ); + "path %s, error: %s, config file content:\n%s", + path.c_str(), e.what(), data.c_str() ); if ( !updateConfigFile ) return; // Recreate it @@ -745,6 +745,10 @@ void LSPClientPlugin::loadLSPConfig( std::vector& lsps, const std nullptr, true, true ); } + if ( updateConfigFile ) { + mConfigHash = String::hash( data ); + } + if ( j.contains( "config" ) ) { auto& config = j["config"]; if ( config.contains( "hover_delay" ) ) diff --git a/src/tools/ecode/plugins/lsp/lspclientplugin.hpp b/src/tools/ecode/plugins/lsp/lspclientplugin.hpp index 4b523c4d7..309312944 100644 --- a/src/tools/ecode/plugins/lsp/lspclientplugin.hpp +++ b/src/tools/ecode/plugins/lsp/lspclientplugin.hpp @@ -43,6 +43,8 @@ class LSPClientPlugin : public Plugin { std::string getDescription() { return Definition().description; } + virtual String::HashType getConfigFileHash() { return mConfigHash; } + void onRegister( UICodeEditor* ); void onUnregister( UICodeEditor* ); @@ -109,6 +111,7 @@ class LSPClientPlugin : public Plugin { Uint32 mOldTextAlign{ 0 }; LSPDiagnosticsCodeAction mQuickFix; std::unordered_set mSemanticHighlightingDisabledLangs; + String::HashType mConfigHash{ 0 }; LSPClientPlugin( PluginManager* pluginManager, bool sync ); diff --git a/src/tools/ecode/plugins/lsp/lspdocumentclient.cpp b/src/tools/ecode/plugins/lsp/lspdocumentclient.cpp index e4561c0bf..269b6966f 100644 --- a/src/tools/ecode/plugins/lsp/lspdocumentclient.cpp +++ b/src/tools/ecode/plugins/lsp/lspdocumentclient.cpp @@ -140,11 +140,12 @@ void LSPDocumentClient::requestSemanticHighlighting() { LSPDocumentClient* docClient = this; URI uri = mDoc->getURI(); LSPClientServer* server = mServer; + Uint64 docModId = mDoc->getModificationId(); mServer->documentSemanticTokensFull( mDoc->getURI(), delta, reqId, range, - [docClient, uri, server]( const auto&, const LSPSemanticTokensDelta& deltas ) { + [docClient, uri, server, docModId]( const auto&, const LSPSemanticTokensDelta& deltas ) { if ( server->hasDocument( uri ) ) - docClient->processTokens( deltas ); + docClient->processTokens( deltas, docModId ); } ); } @@ -219,7 +220,13 @@ static std::string semanticTokenTypeToSyntaxType( const std::string& type, return "normal"; } -void LSPDocumentClient::processTokens( const LSPSemanticTokensDelta& tokens ) { +void LSPDocumentClient::processTokens( const LSPSemanticTokensDelta& tokens, + const Uint64& docModificationId ) { + // If the document has already being modified after requesting the semantic highlighting, + // re-request the changes + if ( docModificationId != mDoc->getModificationId() ) + return requestSemanticHighlightingDelayed(); + mRunningSemanticTokens = true; if ( !tokens.resultId.empty() ) diff --git a/src/tools/ecode/plugins/lsp/lspdocumentclient.hpp b/src/tools/ecode/plugins/lsp/lspdocumentclient.hpp index 8de4db3bc..ff68a5183 100644 --- a/src/tools/ecode/plugins/lsp/lspdocumentclient.hpp +++ b/src/tools/ecode/plugins/lsp/lspdocumentclient.hpp @@ -74,7 +74,7 @@ class LSPDocumentClient : public TextDocument::Client { void requestSemanticHighlightingDelayed(); - void processTokens( const LSPSemanticTokensDelta& tokens ); + void processTokens( const LSPSemanticTokensDelta& tokens, const Uint64& docModificationId ); void highlight(); }; diff --git a/src/tools/ecode/plugins/pluginmanager.cpp b/src/tools/ecode/plugins/pluginmanager.cpp index f3ba8f33e..227f76178 100644 --- a/src/tools/ecode/plugins/pluginmanager.cpp +++ b/src/tools/ecode/plugins/pluginmanager.cpp @@ -15,8 +15,10 @@ PluginManager::PluginManager( const std::string& resourcesPath, const std::strin PluginManager::~PluginManager() { mClosing = true; - for ( auto& plugin : mPlugins ) + for ( auto& plugin : mPlugins ) { + Log::debug( "PluginManager: unloading plugin %s", plugin.second->getTitle().c_str() ); eeDelete( plugin.second ); + } } void PluginManager::registerPlugin( const PluginDefinition& def ) { @@ -34,6 +36,7 @@ bool PluginManager::setEnabled( const std::string& id, bool enable, bool sync ) mPluginsEnabled[id] = enable; UICodeEditorPlugin* plugin = get( id ); if ( enable && plugin == nullptr && hasDefinition( id ) ) { + Log::debug( "PluginManager: loading plugin %s", mDefinitions[id].name.c_str() ); UICodeEditorPlugin* newPlugin = sync && mDefinitions[id].creatorSyncFn ? mDefinitions[id].creatorSyncFn( this ) : mDefinitions[id].creatorFn( this ); @@ -43,6 +46,7 @@ bool PluginManager::setEnabled( const std::string& id, bool enable, bool sync ) return true; } if ( !enable && plugin != nullptr ) { + Log::debug( "PluginManager: unloading plugin %s", mDefinitions[id].name.c_str() ); mThreadPool->run( [plugin]() { eeDelete( plugin ); } ); { Lock l( mSubscribedPluginsMutex ); @@ -59,7 +63,7 @@ bool PluginManager::isEnabled( const std::string& id ) const { bool PluginManager::reload( const std::string& id ) { if ( isEnabled( id ) ) { - Log::warning( "PluginManager reloading plugin: %s", id.c_str() ); + Log::warning( "PluginManager: reloading plugin %s", id.c_str() ); setEnabled( id, false ); setEnabled( id, true ); return true; @@ -446,10 +450,18 @@ void Plugin::subscribeFileSystemListener() { return; if ( !mShuttingDown && file.getFilepath() == mConfigPath && file.getModificationTime() != mConfigFileInfo.getModificationTime() ) { - mConfigFileInfo = file; - mManager->getFileSystemListener()->removeListener( mFileSystemListenerCb ); - mFileSystemListenerCb = 0; - mManager->reload( getId() ); + std::string fileContents; + FileSystem::fileGet( file.getFilepath(), fileContents ); + if ( getConfigFileHash() != String::hash( fileContents ) ) { + mConfigFileInfo = file; + mManager->getFileSystemListener()->removeListener( mFileSystemListenerCb ); + mFileSystemListenerCb = 0; + mManager->reload( getId() ); + } else { + Log::debug( "Plugin %s: Configuration file has been modified: %s. But contents " + "are the same.", + getTitle().c_str(), mConfigPath.c_str() ); + } } } ); } diff --git a/src/tools/ecode/plugins/pluginmanager.hpp b/src/tools/ecode/plugins/pluginmanager.hpp index 301083266..0c6b6c0ac 100644 --- a/src/tools/ecode/plugins/pluginmanager.hpp +++ b/src/tools/ecode/plugins/pluginmanager.hpp @@ -378,6 +378,8 @@ class Plugin : public UICodeEditorPlugin { PluginManager* getManager() const; + virtual String::HashType getConfigFileHash() { return 0; } + protected: PluginManager* mManager{ nullptr }; std::shared_ptr mThreadPool; diff --git a/src/tools/ecode/projectbuild.cpp b/src/tools/ecode/projectbuild.cpp index 73fce619b..519c4b5ef 100644 --- a/src/tools/ecode/projectbuild.cpp +++ b/src/tools/ecode/projectbuild.cpp @@ -253,8 +253,8 @@ bool ProjectBuildManager::load() { j = json::parse( data, nullptr, true, true ); } catch ( const json::exception& e ) { Log::error( "ProjectBuildManager::load - Error parsing project build config from " - "path %s, error: ", - mProjectFile.c_str(), e.what() ); + "path %s, error: %s, config file content:\n%s", + mProjectFile.c_str(), e.what(), mProjectFile.c_str() ); return false; }