diff --git a/include/eepp/core/string.hpp b/include/eepp/core/string.hpp index f828dc5f9..29661fa8e 100644 --- a/include/eepp/core/string.hpp +++ b/include/eepp/core/string.hpp @@ -246,7 +246,8 @@ class EE_API String { */ static bool contains( const String& haystack, const String& needle ); - static int fuzzyMatch( const std::string& string, const std::string& pattern ); + static int fuzzyMatch( const std::string& string, const std::string& pattern, + bool allowUneven = false ); /** Replace all occurrences of the search string with the replacement string. */ static void replaceAll( std::string& target, const std::string& that, const std::string& with ); diff --git a/include/eepp/ui/doc/textdocument.hpp b/include/eepp/ui/doc/textdocument.hpp index 918b98fc9..63de0371f 100644 --- a/include/eepp/ui/doc/textdocument.hpp +++ b/include/eepp/ui/doc/textdocument.hpp @@ -479,7 +479,8 @@ class EE_API TextDocument { std::map mCommands; String mNonWordChars; Client* mActiveClient{ nullptr }; - Mutex mLoadingMutex; + mutable Mutex mLoadingMutex; + mutable Mutex mLoadingFilePathMutex; void initializeCommands(); diff --git a/src/eepp/core/string.cpp b/src/eepp/core/string.cpp index 2f63404bd..7451140ec 100644 --- a/src/eepp/core/string.cpp +++ b/src/eepp/core/string.cpp @@ -601,7 +601,7 @@ String::StringBaseType String::lastChar() const { } // Lite (https://github.com/rxi/lite) fuzzy match implementation -template constexpr int tFuzzyMatch( const T* str, const T* ptn ) { +template constexpr int tFuzzyMatch( const T* str, const T* ptn, bool allowUneven ) { int score = 0; int run = 0; while ( *str && *ptn ) { @@ -619,13 +619,13 @@ template constexpr int tFuzzyMatch( const T* str, const T* ptn ) { } str++; } - if ( *ptn ) + if ( *ptn && !allowUneven ) return INT_MIN; return score - strlen( str ); } -int String::fuzzyMatch( const std::string& string, const std::string& pattern ) { - return tFuzzyMatch( string.c_str(), pattern.c_str() ); +int String::fuzzyMatch( const std::string& string, const std::string& pattern, bool allowUneven ) { + return tFuzzyMatch( string.c_str(), pattern.c_str(), allowUneven ); } std::vector String::stringToUint8( const std::string& str ) { diff --git a/src/eepp/ui/doc/textdocument.cpp b/src/eepp/ui/doc/textdocument.cpp index c09f57cab..e685994cb 100644 --- a/src/eepp/ui/doc/textdocument.cpp +++ b/src/eepp/ui/doc/textdocument.cpp @@ -315,6 +315,7 @@ void TextDocument::toLowerSelection() { } const std::string& TextDocument::getLoadingFilePath() const { + Lock l( mLoadingFilePathMutex ); return mLoadingFilePath; } @@ -346,13 +347,20 @@ bool TextDocument::loadAsyncFromFile( const std::string& path, std::shared_ptr onLoaded ) { mLoading = true; mLoadingAsync = true; - mLoadingFilePath = path; + { + Lock l( mLoadingFilePathMutex ); + mLoadingFilePath = path; + } pool->run( [this, path, onLoaded] { auto loaded = loadFromFile( path ); - if ( loaded != LoadStatus::Interrupted && onLoaded ) + if ( loaded != LoadStatus::Interrupted && onLoaded ) { onLoaded( this, loaded == LoadStatus::Loaded ); - mLoadingFilePath.clear(); + } + { + Lock l( mLoadingFilePathMutex ); + mLoadingFilePath.clear(); + } mLoadingAsync = false; notifyDocumentLoaded(); }, diff --git a/src/tools/ecode/ecode.cpp b/src/tools/ecode/ecode.cpp index a7f5898e3..4ea01bc99 100644 --- a/src/tools/ecode/ecode.cpp +++ b/src/tools/ecode/ecode.cpp @@ -2086,8 +2086,10 @@ void App::updateDocInfo( TextDocument& doc ) { void App::syncProjectTreeWithEditor( UICodeEditor* editor ) { if ( mConfig.editor.syncProjectTreeWithEditor && editor != nullptr && - editor->getDocument().hasFilepath() ) { - std::string path = editor->getDocument().getFilePath(); + ( editor->getDocument().hasFilepath() || + !editor->getDocument().getLoadingFilePath().empty() ) ) { + std::string loadingPath( editor->getDocument().getLoadingFilePath() ); + std::string path = !loadingPath.empty() ? loadingPath : editor->getDocument().getFilePath(); if ( path.size() >= mCurrentProject.size() ) { path = path.substr( mCurrentProject.size() ); mProjectTreeView->setFocusOnSelection( false ); diff --git a/src/tools/ecode/plugins/lsp/lspclientserver.cpp b/src/tools/ecode/plugins/lsp/lspclientserver.cpp index 35978c8ed..be0211a5f 100644 --- a/src/tools/ecode/plugins/lsp/lspclientserver.cpp +++ b/src/tools/ecode/plugins/lsp/lspclientserver.cpp @@ -1362,32 +1362,37 @@ LSPClientServer::documentImplementation( const URI& document, const TextPosition return getAndGoToLocation( document, pos, "textDocument/implementation" ); } -static URI switchHeaderSourceName( const URI& uri ) { +static std::vector switchHeaderSourceName( const URI& uri ) { + std::string basePath( "file://" + FileSystem::fileRemoveExtension( uri.getPath() ) ); if ( FileSystem::fileExtension( uri.getPath() ) == "cpp" ) { - return URI( "file://" + FileSystem::fileRemoveExtension( uri.getPath() ) + ".hpp" ); + return { basePath + ".hpp", basePath + ".h" }; } else if ( FileSystem::fileExtension( uri.getPath() ) == "hpp" ) { - return URI( "file://" + FileSystem::fileRemoveExtension( uri.getPath() ) + ".cpp" ); + return { basePath + ".cpp" }; } else if ( FileSystem::fileExtension( uri.getPath() ) == "c" ) { - return URI( "file://" + FileSystem::fileRemoveExtension( uri.getPath() ) + ".h" ); + return { basePath + ".h" }; } else if ( FileSystem::fileExtension( uri.getPath() ) == "h" ) { - return URI( "file://" + FileSystem::fileRemoveExtension( uri.getPath() ) + ".c" ); + return { basePath + ".c" }; } return {}; } LSPClientServer::LSPRequestHandle LSPClientServer::switchSourceHeader( const URI& document ) { - return send( newRequest( "textDocument/switchSourceHeader", textDocumentURI( document ) ), - [this, document]( const IdType&, json res ) { - URI uri( switchHeaderSourceName( document ) ); - std::string name( FileSystem::fileNameFromPath( uri.getPath() ) ); - if ( res.is_string() && - ( uri.empty() || - FileSystem::fileNameFromPath( res.get() ) == name ) ) { - mManager->goToLocation( { res.get(), TextRange() } ); - } else if ( !uri.empty() ) { - mManager->findAndOpenClosestURI( uri ); - } - } ); + return send( + newRequest( "textDocument/switchSourceHeader", textDocumentURI( document ) ), + [this, document]( const IdType&, json res ) { + std::vector uris( switchHeaderSourceName( document ) ); + for ( const auto& uri : uris ) { + if ( res.is_string() && + ( uri.empty() || FileSystem::fileNameFromPath( res.get() ) == + FileSystem::fileNameFromPath( uri.getPath() ) ) ) { + mManager->goToLocation( { res.get(), TextRange() } ); + break; + } else if ( !uri.empty() ) { + mManager->findAndOpenClosestURI( uris ); + break; + } + } + } ); } LSPClientServer::LSPRequestHandle diff --git a/src/tools/ecode/plugins/lsp/lspclientservermanager.cpp b/src/tools/ecode/plugins/lsp/lspclientservermanager.cpp index 77cf13dd9..1428d261b 100644 --- a/src/tools/ecode/plugins/lsp/lspclientservermanager.cpp +++ b/src/tools/ecode/plugins/lsp/lspclientservermanager.cpp @@ -188,9 +188,11 @@ LSPClientPlugin* LSPClientServerManager::getPlugin() const { return mPlugin; } -void LSPClientServerManager::findAndOpenClosestURI( const URI uri ) { +void LSPClientServerManager::findAndOpenClosestURI( const std::vector& uris ) { json data; - data["uri"] = uri.toString(); + data["uri"] = json::array(); + for ( const auto& uri : uris ) + data["uri"].push_back( uri.toString() ); mPluginManager->sendRequest( mPlugin, PluginMessageType::FindAndOpenClosestURI, PluginMessageFormat::JSON, &data ); } diff --git a/src/tools/ecode/plugins/lsp/lspclientservermanager.hpp b/src/tools/ecode/plugins/lsp/lspclientservermanager.hpp index e5ec7a62d..df71086dc 100644 --- a/src/tools/ecode/plugins/lsp/lspclientservermanager.hpp +++ b/src/tools/ecode/plugins/lsp/lspclientservermanager.hpp @@ -58,7 +58,7 @@ class LSPClientServerManager { LSPClientPlugin* getPlugin() const; - void findAndOpenClosestURI( const URI uri ); + void findAndOpenClosestURI( const std::vector& uris ); protected: friend class LSPClientServer; diff --git a/src/tools/ecode/projectdirectorytree.cpp b/src/tools/ecode/projectdirectorytree.cpp index 685a11df1..768c13294 100644 --- a/src/tools/ecode/projectdirectorytree.cpp +++ b/src/tools/ecode/projectdirectorytree.cpp @@ -2,6 +2,7 @@ #include "ecode.hpp" #include #include +#include namespace ecode { @@ -84,6 +85,29 @@ void ProjectDirectoryTree::scan( const ProjectDirectoryTree::ScanCompleteEvent& #endif } +std::shared_ptr +ProjectDirectoryTree::fuzzyMatchTree( const std::vector& matches, + const size_t& max ) const { + Lock rl( mMatchingMutex ); + std::multimap> matchesMap; + std::vector files; + std::vector names; + for ( const auto& match : matches ) { + for ( size_t i = 0; i < mNames.size(); i++ ) { + int matchName = String::fuzzyMatch( mNames[i], match ); + int matchPath = String::fuzzyMatch( mFiles[i], match ); + matchesMap.insert( { std::max( matchName, matchPath ), i } ); + } + } + for ( auto& res : matchesMap ) { + if ( names.size() < max ) { + names.emplace_back( mNames[res.second] ); + files.emplace_back( mFiles[res.second] ); + } + } + return std::make_shared( files, names ); +} + std::shared_ptr ProjectDirectoryTree::fuzzyMatchTree( const std::string& match, const size_t& max ) const { Lock rl( mMatchingMutex ); @@ -394,27 +418,52 @@ PluginRequestHandle ProjectDirectoryTree::processMessage( const PluginMessage& m if ( !json.contains( "uri" ) ) return {}; - URI uri = json.at( "uri" ).get(); + nlohmann::json juris = json.at( "uri" ); + std::vector expectedNames; + std::vector tentativePaths; + for ( const auto& turi : juris ) { + std::string path( URI( turi.get() ).getPath() ); + tentativePaths.emplace_back( path ); + expectedNames.emplace_back( FileSystem::fileNameFromPath( path ) ); + } - std::string fileName( FileSystem::fileNameFromPath( uri.getPath() ) ); - auto model = fuzzyMatchTree( fileName, 10 ); + auto model = fuzzyMatchTree( expectedNames, 10 ); size_t rowCount = model->rowCount( {} ); if ( rowCount == 0 ) return {}; + std::map> matchesMap; + for ( size_t i = 0; i < rowCount; ++i ) { - Variant data = model->data( model->index( i, 1 ) ); - if ( data.is( Variant::Type::cstr ) ) { - std::string filePath( data.asCStr() ); - if ( FileSystem::fileNameFromPath( filePath ) == fileName ) { - mApp->getUISceneNode()->runOnMainThread( - [this, filePath]() { mApp->loadFileFromPathOrFocus( filePath ); } ); - break; + Variant dataName = model->data( model->index( i, 0 ) ); + Variant dataPath = model->data( model->index( i, 1 ) ); + if ( dataName.is( Variant::Type::cstr ) && dataPath.is( Variant::Type::cstr ) ) { + std::string fileName( dataName.asCStr() ); + std::string filePath( dataPath.asCStr() ); + if ( std::find( expectedNames.begin(), expectedNames.end(), fileName ) != + expectedNames.end() ) { + std::string closestDataPath; + int max{ std::numeric_limits::min() }; + for ( const auto& paths : tentativePaths ) { + int res = String::fuzzyMatch( filePath.c_str(), paths.c_str(), true ); + if ( res > max ) { + closestDataPath = filePath; + max = res; + } + } + if ( !closestDataPath.empty() ) + matchesMap[max] = closestDataPath; } } } + if ( !matchesMap.empty() ) { + std::string filePath( matchesMap.begin()->second ); + mApp->getUISceneNode()->runOnMainThread( + [this, filePath]() { mApp->loadFileFromPathOrFocus( filePath ); } ); + } + return PluginRequestHandle::broadcast(); } diff --git a/src/tools/ecode/projectdirectorytree.hpp b/src/tools/ecode/projectdirectorytree.hpp index 022caa812..744ba7013 100644 --- a/src/tools/ecode/projectdirectorytree.hpp +++ b/src/tools/ecode/projectdirectorytree.hpp @@ -102,6 +102,9 @@ class ProjectDirectoryTree { const std::vector& acceptedPatterns = {}, const bool& ignoreHidden = true ); + std::shared_ptr fuzzyMatchTree( const std::vector& matches, + const size_t& max ) const; + std::shared_ptr fuzzyMatchTree( const std::string& match, const size_t& max ) const;