diff --git a/src/eepp/system/log.cpp b/src/eepp/system/log.cpp index 30ae0c749..01bd4bfa3 100644 --- a/src/eepp/system/log.cpp +++ b/src/eepp/system/log.cpp @@ -31,6 +31,10 @@ Log* Log::create( const std::string& logPath, const LogLevel& level, bool consol bool liveWrite ) { if ( NULL == ms_singleton ) { ms_singleton = eeNew( Log, ( logPath, level, consoleOutput, liveWrite ) ); + } else { + ms_singleton->setLogLevelThreshold( level ); + ms_singleton->setConsoleOutput( consoleOutput ); + ms_singleton->setLiveWrite( liveWrite ); } return ms_singleton; } @@ -38,6 +42,10 @@ Log* Log::create( const std::string& logPath, const LogLevel& level, bool consol Log* Log::create( const LogLevel& level, bool consoleOutput, bool liveWrite ) { if ( NULL == ms_singleton ) { ms_singleton = eeNew( Log, ( "", level, consoleOutput, liveWrite ) ); + } else { + ms_singleton->setLogLevelThreshold( level ); + ms_singleton->setConsoleOutput( consoleOutput ); + ms_singleton->setLiveWrite( liveWrite ); } return ms_singleton; } diff --git a/src/tools/ecode/ecode.cpp b/src/tools/ecode/ecode.cpp index cc0951c62..46d4e1702 100644 --- a/src/tools/ecode/ecode.cpp +++ b/src/tools/ecode/ecode.cpp @@ -460,6 +460,11 @@ void App::loadConfig( const LogLevel& logLevel, const Sizeu& displaySize, bool s Log::create( mConfigPath + "ecode.log", logLevel, true, true ); #endif + if ( !mArgs.empty() ) { + std::string strargs( String::join( mArgs ) ); + Log::info( "ecode starting with these command line arguments: %s", strargs.c_str() ); + } + initPluginManager(); mConfig.load( mConfigPath, mKeybindingsPath, mInitColorScheme, mRecentFiles, mRecentFolders, @@ -602,7 +607,8 @@ void App::onTextDropped( String text ) { } } -App::App( const size_t& jobs ) : +App::App( const size_t& jobs, const std::vector& args ) : + mArgs( args ), mThreadPool( ThreadPool::createShared( jobs > 0 ? jobs : eemax( 2, Sys::getCPUCount() ) ) ) {} @@ -1404,6 +1410,7 @@ void App::onWidgetFocusChange( UIWidget* widget ) { void App::onCodeEditorFocusChange( UICodeEditor* editor ) { updateDocInfo( editor->getDocument() ); mDocSearchController->onCodeEditorFocusChange( editor ); + mUniversalLocator->onCodeEditorFocusChange( editor ); syncProjectTreeWithEditor( editor ); } @@ -3361,14 +3368,9 @@ EE_MAIN_FUNC int main( int argc, char* argv[] ) { "Convert any JSON language definition to CPP syntax definition (development helper)", { "convert-lang-path" }, "" ); + std::vector args; try { - auto args = Sys::parseArguments( argc, argv ); - - if ( !args.empty() ) { - std::string strargs( String::join( args ) ); - Log::info( "ecode starting with these command line arguments: %s", strargs.c_str() ); - } - + args = Sys::parseArguments( argc, argv ); parser.ParseCLI( args ); } catch ( const args::Help& ) { Sys::windowAttachConsole(); @@ -3417,7 +3419,7 @@ EE_MAIN_FUNC int main( int argc, char* argv[] ) { if ( verbose.Get() ) Log::instance()->setConsoleOutput( true ); - appInstance = eeNew( App, ( jobs ) ); + appInstance = eeNew( App, ( jobs, args ) ); appInstance->init( logLevel.Get(), folder ? folder.Get() : fileOrFolderPos.Get(), pixelDenstiyConf ? pixelDenstiyConf.Get() : 0.f, prefersColorScheme ? prefersColorScheme.Get() : "", terminal.Get(), fb.Get(), diff --git a/src/tools/ecode/ecode.hpp b/src/tools/ecode/ecode.hpp index 05f1c2351..09afb39e5 100644 --- a/src/tools/ecode/ecode.hpp +++ b/src/tools/ecode/ecode.hpp @@ -27,7 +27,7 @@ class SettingsMenu; class App : public UICodeEditorSplitter::Client { public: - App( const size_t& jobs = 0 ); + App( const size_t& jobs = 0, const std::vector& args = {} ); ~App(); @@ -315,6 +315,7 @@ class App : public UICodeEditorSplitter::Client { void loadFileDelayed(); protected: + std::vector mArgs; EE::Window::Window* mWindow{ nullptr }; UISceneNode* mUISceneNode{ nullptr }; UIConsole* mConsole{ nullptr }; diff --git a/src/tools/ecode/plugins/lsp/lspclientplugin.cpp b/src/tools/ecode/plugins/lsp/lspclientplugin.cpp index b4b9cbf03..c949dd7aa 100644 --- a/src/tools/ecode/plugins/lsp/lspclientplugin.cpp +++ b/src/tools/ecode/plugins/lsp/lspclientplugin.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -261,7 +262,7 @@ PluginRequestHandle LSPClientPlugin::processWorkspaceSymbol( const PluginMessage for ( const auto server : servers ) { server->workspaceSymbol( msg.asJSON().value( "query", "" ), - [&]( const PluginIDType& id, const std::vector& info ) { + [&]( const PluginIDType& id, const LSPSymbolInformationList& info ) { mManager->sendResponse( this, PluginMessageType::WorkspaceSymbol, PluginMessageFormat::SymbolInformation, &info, id ); } ); @@ -270,6 +271,42 @@ PluginRequestHandle LSPClientPlugin::processWorkspaceSymbol( const PluginMessage return {}; } +PluginRequestHandle LSPClientPlugin::processTextDocumentSymbol( const PluginMessage& msg ) { + if ( !msg.isRequest() || + ( msg.type != PluginMessageType::TextDocumentSymbol && + msg.type != PluginMessageType::TextDocumentFlattenSymbol ) || + msg.format != PluginMessageFormat::JSON || !msg.asJSON().contains( "uri" ) ) + return {}; + + URI uri( msg.asJSON().value( "uri", "" ) ); + if ( uri.empty() ) + return {}; + + const LSPSymbolInformationList& symbols = msg.type == PluginMessageType::TextDocumentSymbol + ? getDocumentSymbols( uri ) + : getDocumentFlattenSymbols( uri ); + if ( !symbols.empty() ) { + mManager->sendResponse( this, msg.type, PluginMessageFormat::SymbolInformation, &symbols, + uri.toString() ); + return { uri.toString() }; + } + + LSPClientServer* server = mClientManager.getOneLSPClientServer( uri ); + if ( !server || !server->getCapabilities().documentSymbolProvider ) + return {}; + auto handler = [uri, this]( const PluginIDType& id, LSPSymbolInformationList&& res ) { + setDocumentSymbolsFromResponse( id, uri, std::move( res ) ); + }; + if ( Engine::instance()->isMainThread() ) { + server->getThreadPool()->run( + [server, uri, handler]() { server->documentSymbols( uri, handler ); } ); + } else { + server->documentSymbols( uri, handler ); + } + + return { uri.toString() }; +} + bool LSPClientPlugin::processDocumentFormattingResponse( const URI& uri, std::vector edits ) { auto doc = mManager->getSplitter()->findDocFromURI( uri ); @@ -311,6 +348,51 @@ bool LSPClientPlugin::processDocumentFormattingResponse( const URI& uri, return true; } +void LSPClientPlugin::setDocumentSymbols( const URI& docURI, LSPSymbolInformationList&& res ) { + Lock l( mDocSymbolsMutex ); + mDocFlatSymbols[docURI] = !LSPSymbolInformationListHelper::isFlat( res ) + ? LSPSymbolInformationListHelper::flatten( res ) + : res; + mDocSymbols[docURI] = std::move( res ); +} + +void LSPClientPlugin::setDocumentSymbolsFromResponse( const PluginIDType& id, const URI& docURI, + LSPSymbolInformationList&& res ) { + setDocumentSymbols( docURI, std::move( res ) ); + mManager->sendResponse( this, PluginMessageType::TextDocumentSymbol, + PluginMessageFormat::SymbolInformation, &getDocumentSymbols( docURI ), + id ); + mManager->sendResponse( this, PluginMessageType::TextDocumentFlattenSymbol, + PluginMessageFormat::SymbolInformation, + &getDocumentFlattenSymbols( docURI ), id ); +} + +const LSPSymbolInformationList& LSPClientPlugin::getDocumentSymbols( const URI& docURI ) { + Lock l( mDocSymbolsMutex ); + auto foundIt = mDocSymbols.find( docURI ); + if ( foundIt != mDocSymbols.end() ) + return foundIt->second; + mDocSymbols[docURI] = {}; + return mDocSymbols[docURI]; +} + +const LSPSymbolInformationList& LSPClientPlugin::getDocumentFlattenSymbols( const URI& docURI ) { + Lock l( mDocSymbolsMutex ); + auto foundIt = mDocFlatSymbols.find( docURI ); + if ( foundIt != mDocFlatSymbols.end() ) + return foundIt->second; + mDocFlatSymbols[docURI] = {}; + return mDocFlatSymbols[docURI]; +} + +TextDocument* LSPClientPlugin::getDocumentFromURI( const URI& uri ) { + Lock l( mDocMutex ); + for ( const auto client : mDocs ) + if ( client->getURI() == uri ) + return client; + return nullptr; +} + bool LSPClientPlugin::editorExists( UICodeEditor* editor ) { return mManager->getSplitter()->editorExists( editor ); } @@ -495,6 +577,11 @@ PluginRequestHandle LSPClientPlugin::processMessage( const PluginMessage& msg ) return ret; break; } + case PluginMessageType::TextDocumentSymbol: + case PluginMessageType::TextDocumentFlattenSymbol: { + processTextDocumentSymbol( msg ); + break; + } default: break; } @@ -852,9 +939,17 @@ void LSPClientPlugin::onUnregister( UICodeEditor* editor ) { mEditors.erase( editor ); mEditorsTags.erase( editor ); mEditorDocs.erase( editor ); - for ( const auto& ieditor : mEditorDocs ) + for ( const auto& ieditor : mEditorDocs ) { if ( ieditor.second == doc ) return; + } + + { + Lock lds( mDocSymbolsMutex ); + mDocSymbols.erase( doc->getURI() ); + mDocFlatSymbols.erase( doc->getURI() ); + } + mDocs.erase( doc ); } diff --git a/src/tools/ecode/plugins/lsp/lspclientplugin.hpp b/src/tools/ecode/plugins/lsp/lspclientplugin.hpp index 0c0618e20..6ea4d0c8e 100644 --- a/src/tools/ecode/plugins/lsp/lspclientplugin.hpp +++ b/src/tools/ecode/plugins/lsp/lspclientplugin.hpp @@ -25,7 +25,7 @@ class LSPClientPlugin : public UICodeEditorPlugin { public: static PluginDefinition Definition() { return { "lspclient", "LSP Client", "Language Server Protocol Client.", - LSPClientPlugin::New, { 0, 0, 1 }, LSPClientPlugin::NewSync }; + LSPClientPlugin::New, { 0, 0, 2 }, LSPClientPlugin::NewSync }; } static UICodeEditorPlugin* New( PluginManager* pluginManager ); @@ -73,14 +73,27 @@ class LSPClientPlugin : public UICodeEditorPlugin { bool processDocumentFormattingResponse( const URI& uri, std::vector edits ); + const LSPSymbolInformationList& getDocumentSymbols( TextDocument* doc ); + + const LSPSymbolInformationList& getDocumentSymbols( const URI& uri ); + + const LSPSymbolInformationList& getDocumentFlattenSymbols( const URI& uri ); + + TextDocument* getDocumentFromURI( const URI& uri ); + protected: + friend class LSPDocumentClient; + PluginManager* mManager{ nullptr }; std::shared_ptr mThreadPool; Clock mClock; Mutex mDocMutex; + Mutex mDocSymbolsMutex; std::unordered_map> mEditors; std::unordered_map> mEditorsTags; std::set mDocs; + std::map mDocSymbols; + std::map mDocFlatSymbols; std::unordered_map mEditorDocs; LSPClientServerManager mClientManager; std::string mConfigPath; @@ -146,6 +159,13 @@ class LSPClientPlugin : public UICodeEditorPlugin { void createListView( UICodeEditor* editor, const std::shared_ptr& model, const ModelEventCallback& onModelEventCb, const std::function onCreateCb = {} ); + + PluginRequestHandle processTextDocumentSymbol( const PluginMessage& msg ); + + void setDocumentSymbols( const URI& docURI, LSPSymbolInformationList&& res ); + + void setDocumentSymbolsFromResponse( const PluginIDType& id, const URI& docURI, + LSPSymbolInformationList&& res ); }; } // namespace ecode diff --git a/src/tools/ecode/plugins/lsp/lspclientserver.cpp b/src/tools/ecode/plugins/lsp/lspclientserver.cpp index a51f28e89..85272f1c4 100644 --- a/src/tools/ecode/plugins/lsp/lspclientserver.cpp +++ b/src/tools/ecode/plugins/lsp/lspclientserver.cpp @@ -344,17 +344,47 @@ static bool isPositionValid( const TextPosition& pos ) { return pos.column() >= 0 && pos.line() >= 0; } -static std::vector parseDocumentSymbols( const json& result ) { - std::vector ret; - std::map index; +struct LSPSymbolInformationTmp { + LSPSymbolInformationTmp() = default; + LSPSymbolInformationTmp( const std::string& _name, LSPSymbolKind _kind, TextRange _range, + const std::string& _detail ) : + name( _name ), detail( _detail ), kind( _kind ), range( _range ) {} + std::string name; + std::string detail; + LSPSymbolKind kind{ LSPSymbolKind::File }; + URI url; + TextRange range; + TextRange selectionRange; + double score = 0.0; + std::list children; + static LSPSymbolInformation fromTmp( const LSPSymbolInformationTmp& tmp ) { + LSPSymbolInformation info; + info.name = std::move( tmp.name ); + info.detail = std::move( tmp.detail ); + info.kind = std::move( tmp.kind ); + info.url = std::move( tmp.url ); + info.range = std::move( tmp.range ); + info.selectionRange = std::move( tmp.selectionRange ); + info.score = std::move( tmp.score ); + for ( const auto& child : tmp.children ) + info.children.push_back( fromTmp( child ) ); + return info; + } +}; - std::function parseSymbol = - [&]( const json& symbol, LSPSymbolInformation* parent ) { +static LSPSymbolInformationList parseDocumentSymbols( const json& result ) { + // TODO: Optimize this + Clock clock; + std::list ret; + std::map index; + + std::function parseSymbol = + [&]( const json& symbol, LSPSymbolInformationTmp* parent ) { const auto& mrange = symbol.contains( MEMBER_RANGE ) ? symbol.at( MEMBER_RANGE ) : symbol[MEMBER_LOCATION].at( MEMBER_RANGE ); auto range = parseRange( mrange ); - std::map::iterator it = index.end(); + auto it = index.end(); if ( !parent ) { auto container = symbol.value( "containerName", "" ); it = index.find( container ); @@ -386,12 +416,18 @@ static std::vector parseDocumentSymbols( const json& resul const auto& symInfos = result; for ( const auto& info : symInfos ) parseSymbol( info, nullptr ); - return ret; + + LSPSymbolInformationList rret; + for ( const auto& r : ret ) + rret.push_back( LSPSymbolInformationTmp::fromTmp( r ) ); + + Log::debug( "LSPClientServer - parseDocumentSymbols took: %.2fms", + clock.getElapsed().asMilliseconds() ); + return rret; } -static std::vector parseWorkspaceSymbols( const json& res ) { - std::vector symbols; - symbols.reserve( res.size() ); +static LSPSymbolInformationList parseWorkspaceSymbols( const json& res ) { + LSPSymbolInformationList symbols; std::transform( res.cbegin(), res.cend(), std::back_inserter( symbols ), []( const json& symbol ) { @@ -881,6 +917,9 @@ void LSPClientServer::registerCapabilities( const json& jcap ) { if ( reg["method"] == "workspace/executeCommand" ) { mCapabilities.executeCommandProvider = true; registered = true; + } else if ( reg["method"] == "textDocument/documentSymbol" ) { + mCapabilities.documentSymbolProvider = true; + registered = true; } } } @@ -922,7 +961,8 @@ void LSPClientServer::initialize() { json capabilities{ { "textDocument", json{ - { "documentSymbol", json{ { "hierarchicalDocumentSymbolSupport", true } } }, + { "documentSymbol", json{ { "dynamicRegistration", true }, + { "hierarchicalDocumentSymbolSupport", true } } }, { "publishDiagnostics", json{ { "relatedInformation", true }, { "codeActionsInline", true } } }, { "codeAction", codeAction }, @@ -983,6 +1023,7 @@ void LSPClientServer::initialize() { mReady = true; write( newRequest( "initialized" ) ); sendQueuedMessages(); + notifyServerInitialized(); // Broadcast the language capabilities to all the interested plugins mManager->getPluginManager()->sendBroadcast( @@ -1039,6 +1080,11 @@ bool LSPClientServer::registerDoc( const std::shared_ptr& doc ) { return true; } +void LSPClientServer::notifyServerInitialized() { + for ( const auto& client : mClients ) + client.second->onServerInitialized(); +} + bool LSPClientServer::isRunning() { return mProcess.isAlive() && !mProcess.isShootingDown(); } @@ -1229,7 +1275,7 @@ LSPClientServer::LSPRequestHandle LSPClientServer::documentSymbols( const URI& d LSPClientServer::LSPRequestHandle LSPClientServer::documentSymbols( const URI& document, - const ReplyHandler>& h, + const WReplyHandler& h, const ReplyHandler& eh ) { return documentSymbols( document, diff --git a/src/tools/ecode/plugins/lsp/lspclientserver.hpp b/src/tools/ecode/plugins/lsp/lspclientserver.hpp index 35c4f7d2c..2322cbe0d 100644 --- a/src/tools/ecode/plugins/lsp/lspclientserver.hpp +++ b/src/tools/ecode/plugins/lsp/lspclientserver.hpp @@ -32,11 +32,13 @@ class LSPClientServer { using IdType = PluginIDType; template using ReplyHandler = std::function; + template using WReplyHandler = std::function; + using JsonReplyHandler = ReplyHandler; using CodeActionHandler = ReplyHandler>; using HoverHandler = ReplyHandler; using CompletionHandler = ReplyHandler; - using SymbolInformationHandler = ReplyHandler>; + using SymbolInformationHandler = WReplyHandler; using SelectionRangeHandler = ReplyHandler>>; using SignatureHelpHandler = ReplyHandler; using LocationHandler = ReplyHandler>; @@ -82,8 +84,8 @@ class LSPClientServer { const JsonReplyHandler& eh ); LSPRequestHandle documentSymbols( const URI& document, - const ReplyHandler>& h, - const ReplyHandler& eh ); + const WReplyHandler& h, + const ReplyHandler& eh = {} ); LSPRequestHandle didOpen( const URI& document, const std::string& text, int version ); @@ -254,6 +256,8 @@ class LSPClientServer { void processRequest( const json& msg ); void goToLocation( const json& res ); + + void notifyServerInitialized(); }; } // namespace ecode diff --git a/src/tools/ecode/plugins/lsp/lspclientservermanager.cpp b/src/tools/ecode/plugins/lsp/lspclientservermanager.cpp index ca9962daa..d5e3f005f 100644 --- a/src/tools/ecode/plugins/lsp/lspclientservermanager.cpp +++ b/src/tools/ecode/plugins/lsp/lspclientservermanager.cpp @@ -86,22 +86,23 @@ void LSPClientServerManager::tryRunServer( const std::shared_ptr& auto rootPath = findRootPath( lsp, doc ); auto lspName = lsp.name.empty() ? lsp.command : lsp.name; String::HashType id = String::hash( lspName + "|" + lsp.language + "|" + rootPath ); - Lock l( mClientsMutex ); - auto clientIt = mClients.find( id ); LSPClientServer* server = nullptr; - if ( clientIt == mClients.end() ) { - std::unique_ptr serverUP = runLSPServer( id, lsp, rootPath ); - if ( ( server = serverUP.get() ) ) { - mClients[id] = std::move( serverUP ); - if ( !mLSPWorkspaceFolder.uri.empty() ) - server->didChangeWorkspaceFolders( { mLSPWorkspaceFolder }, {} ); + { + Lock l( mClientsMutex ); + auto clientIt = mClients.find( id ); + if ( clientIt == mClients.end() ) { + std::unique_ptr serverUP = runLSPServer( id, lsp, rootPath ); + if ( ( server = serverUP.get() ) ) { + mClients[id] = std::move( serverUP ); + if ( !mLSPWorkspaceFolder.uri.empty() ) + server->didChangeWorkspaceFolders( { mLSPWorkspaceFolder }, {} ); + } + } else { + server = clientIt->second.get(); } - } else { - server = clientIt->second.get(); } - if ( server ) { + if ( server ) server->registerDoc( doc ); - } } } diff --git a/src/tools/ecode/plugins/lsp/lspdocumentclient.cpp b/src/tools/ecode/plugins/lsp/lspdocumentclient.cpp index 18cf4d5ad..c1acb34fc 100644 --- a/src/tools/ecode/plugins/lsp/lspdocumentclient.cpp +++ b/src/tools/ecode/plugins/lsp/lspdocumentclient.cpp @@ -1,5 +1,7 @@ #include "lspdocumentclient.hpp" +#include "lspclientplugin.hpp" #include "lspclientserver.hpp" +#include "lspclientservermanager.hpp" #include #include #include @@ -11,10 +13,16 @@ namespace ecode { LSPDocumentClient::LSPDocumentClient( LSPClientServer* server, TextDocument* doc ) : mServer( server ), mDoc( doc ) { + refreshTag(); notifyOpen(); + requestSymbolsDelayed(); } -LSPDocumentClient::~LSPDocumentClient() {} +LSPDocumentClient::~LSPDocumentClient() { + UISceneNode* sceneNode = getUISceneNode(); + if ( nullptr != sceneNode && 0 != mTag ) + sceneNode->removeActionsByTag( mTag ); +} void LSPDocumentClient::onDocumentTextChanged( const DocumentContentChange& change ) { ++mVersion; @@ -22,6 +30,7 @@ void LSPDocumentClient::onDocumentTextChanged( const DocumentContentChange& chan // executed in FIFO. Se we accumulate the events in a queue and fire them in correct order. mServer->queueDidChange( mDoc->getURI(), mVersion, "", { change } ); mServer->getThreadPool()->run( [&, change]() { mServer->processDidChangeQueue(); } ); + requestSymbolsDelayed(); } void LSPDocumentClient::onDocumentUndoRedo( const TextDocument::UndoRedo& /*eventType*/ ) {} @@ -48,7 +57,9 @@ void LSPDocumentClient::onDocumentClosed( TextDocument* ) { void LSPDocumentClient::onDocumentDirtyOnFileSystem( TextDocument* ) {} -void LSPDocumentClient::onDocumentMoved( TextDocument* ) {} +void LSPDocumentClient::onDocumentMoved( TextDocument* ) { + refreshTag(); +} void LSPDocumentClient::onDocumentReloaded( TextDocument* ) { URI uri = mDoc->getURI(); @@ -59,6 +70,7 @@ void LSPDocumentClient::onDocumentReloaded( TextDocument* ) { server->didClose( uri ); server->didOpen( doc, version ); } ); + refreshTag(); } TextDocument* LSPDocumentClient::getDoc() const { @@ -73,6 +85,26 @@ int LSPDocumentClient::getVersion() const { return mVersion; } +void LSPDocumentClient::onServerInitialized() { + requestSymbols(); +} + +void LSPDocumentClient::refreshTag() { + String::HashType oldTag = mTag; + mTag = String::hash( mDoc->getURI().toString() ); + UISceneNode* sceneNode = getUISceneNode(); + if ( nullptr != sceneNode && 0 != oldTag ) + sceneNode->removeActionsByTag( oldTag ); +} + +UISceneNode* LSPDocumentClient::getUISceneNode() { + LSPClientServer* server = mServer; + if ( !server || !server->getManager() || !server->getManager()->getPluginManager() || + !server->getManager()->getPluginManager()->getUISceneNode() ) + return nullptr; + return server->getManager()->getPluginManager()->getUISceneNode(); +} + void LSPDocumentClient::notifyOpen() { eeASSERT( mDoc ); if ( Engine::instance()->isMainThread() ) { @@ -82,4 +114,32 @@ void LSPDocumentClient::notifyOpen() { } } +void LSPDocumentClient::requestSymbols() { + eeASSERT( mDoc ); + LSPClientServer* server = mServer; + if ( !server->getCapabilities().documentSymbolProvider ) + return; + URI uri = mDoc->getURI(); + auto handler = [uri, server]( const PluginIDType& id, LSPSymbolInformationList&& res ) { + server->getManager()->getPlugin()->setDocumentSymbolsFromResponse( id, uri, + std::move( res ) ); + }; + if ( Engine::instance()->isMainThread() ) { + mServer->getThreadPool()->run( + [server, uri, handler]() { server->documentSymbols( uri, handler ); } ); + } else { + server->documentSymbols( uri, handler ); + } +} + +void LSPDocumentClient::requestSymbolsDelayed() { + if ( !mServer || !mServer->getCapabilities().documentSymbolProvider ) + return; + UISceneNode* sceneNode = getUISceneNode(); + if ( sceneNode ) { + sceneNode->removeActionsByTag( mTag ); + sceneNode->runOnMainThread( [this]() { requestSymbols(); }, Seconds( 1.f ), mTag ); + } +} + } // namespace ecode diff --git a/src/tools/ecode/plugins/lsp/lspdocumentclient.hpp b/src/tools/ecode/plugins/lsp/lspdocumentclient.hpp index bae61e80d..752188ebd 100644 --- a/src/tools/ecode/plugins/lsp/lspdocumentclient.hpp +++ b/src/tools/ecode/plugins/lsp/lspdocumentclient.hpp @@ -6,8 +6,13 @@ using namespace EE; using namespace EE::System; +using namespace EE::UI; using namespace EE::UI::Doc; +namespace EE { namespace UI { +class UISceneNode; +}} // namespace EE::UI + namespace ecode { class LSPClientServer; @@ -32,16 +37,27 @@ class LSPDocumentClient : public TextDocument::Client { void notifyOpen(); + void requestSymbols(); + + void requestSymbolsDelayed(); + TextDocument* getDoc() const; LSPClientServer* getServer() const; int getVersion() const; + void onServerInitialized(); + protected: LSPClientServer* mServer{ nullptr }; TextDocument* mDoc{ nullptr }; + String::HashType mTag{ 0 }; int mVersion{ 0 }; + + void refreshTag(); + + UISceneNode* getUISceneNode(); }; } // namespace ecode diff --git a/src/tools/ecode/plugins/lsp/lspprotocol.hpp b/src/tools/ecode/plugins/lsp/lspprotocol.hpp index ddfd46b21..bcde27196 100644 --- a/src/tools/ecode/plugins/lsp/lspprotocol.hpp +++ b/src/tools/ecode/plugins/lsp/lspprotocol.hpp @@ -321,6 +321,34 @@ struct LSPSymbolInformation { std::vector children; }; +using LSPSymbolInformationList = std::vector; + +struct LSPSymbolInformationListHelper { + static bool isFlat( const LSPSymbolInformationList& list ) { + for ( const auto& l : list ) + if ( !l.children.empty() ) + return false; + return true; + } + + static LSPSymbolInformationList flatten( const LSPSymbolInformationList& list ) { + LSPSymbolInformationList newList; + for ( const auto& l : list ) { + if ( l.children.empty() ) { + newList.push_back( l ); + } else { + auto nl = l; + nl.children.clear(); + newList.push_back( nl ); + auto clist = flatten( l.children ); + for ( const auto& cl : clist ) + newList.push_back( cl ); + } + } + return newList; + } +}; + using LSPWorkDoneProgressParams = LSPProgressParams; struct LSPResponseError { diff --git a/src/tools/ecode/plugins/pluginmanager.cpp b/src/tools/ecode/plugins/pluginmanager.cpp index ca4924646..6f4fa52e0 100644 --- a/src/tools/ecode/plugins/pluginmanager.cpp +++ b/src/tools/ecode/plugins/pluginmanager.cpp @@ -105,6 +105,10 @@ UICodeEditorSplitter* PluginManager::getSplitter() const { return mSplitter; } +UISceneNode* PluginManager::getUISceneNode() const { + return mSplitter ? mSplitter->getUISceneNode() : nullptr; +} + const std::string& PluginManager::getWorkspaceFolder() const { return mWorkspaceFolder; } diff --git a/src/tools/ecode/plugins/pluginmanager.hpp b/src/tools/ecode/plugins/pluginmanager.hpp index 4482ac9f7..4283d9075 100644 --- a/src/tools/ecode/plugins/pluginmanager.hpp +++ b/src/tools/ecode/plugins/pluginmanager.hpp @@ -71,9 +71,11 @@ enum class PluginMessageType { FindAndOpenClosestURI, // Request a component to find and open the closest path from an URI DocumentFormatting, // Request the LSP Server to format a document SymbolReference, // Request the LSP Server to find a symbol reference in the project - ShowMessage, // The LSP server sends a request to the client to show a message on screen - ShowDocument, // The LSP server sends a request to the client to show a document - WorkspaceSymbol, + ShowMessage, // The LSP server sends a request to the client to show a message on screen + ShowDocument, // The LSP server sends a request to the client to show a document + WorkspaceSymbol, // Request to the LSP server to query workspace symbols + TextDocumentSymbol, // Request to the LSP server the document symbols + TextDocumentFlattenSymbol, // Request to the LSP server the document symbols flattened Undefined }; @@ -174,8 +176,8 @@ struct PluginMessage { return *static_cast( data ); } - const std::vector& asSymbolInformation() const { - return *static_cast*>( data ); + const LSPSymbolInformationList& asSymbolInformation() const { + return *static_cast( data ); } const PluginIDType& asPluginID() const { return *static_cast( data ); } @@ -249,6 +251,8 @@ class PluginManager { UICodeEditorSplitter* getSplitter() const; + UISceneNode* getUISceneNode() const; + const std::string& getWorkspaceFolder() const; void setWorkspaceFolder( const std::string& workspaceFolder ); @@ -298,7 +302,6 @@ class PluginManager { bool hasDefinition( const std::string& id ); void setSplitter( UICodeEditorSplitter* splitter ); - }; class PluginsModel : public Model { diff --git a/src/tools/ecode/universallocator.cpp b/src/tools/ecode/universallocator.cpp index e54c33096..550f3846e 100644 --- a/src/tools/ecode/universallocator.cpp +++ b/src/tools/ecode/universallocator.cpp @@ -5,15 +5,16 @@ namespace ecode { class LSPSymbolInfoModel : public Model { public: - static std::shared_ptr - create( UISceneNode* uiSceneNode, const std::string& query, - const std::vector& data ) { - return std::make_shared( uiSceneNode, query, data ); + static std::shared_ptr create( UISceneNode* uiSceneNode, + const std::string& query, + const LSPSymbolInformationList& data, + bool displayLine = false ) { + return std::make_shared( uiSceneNode, query, data, displayLine ); } explicit LSPSymbolInfoModel( UISceneNode* uiSceneNode, const std::string& query, - const std::vector& info ) : - mUISceneNode( uiSceneNode ), mQuery( query ), mInfo( info ) {} + const LSPSymbolInformationList& info, bool displayLine ) : + mUISceneNode( uiSceneNode ), mQuery( query ), mInfo( info ), mDisplayLine( displayLine ) {} size_t rowCount( const ModelIndex& ) const override { return mInfo.size(); }; @@ -26,8 +27,17 @@ class LSPSymbolInfoModel : public Model { switch ( index.column() ) { case 0: return Variant( mInfo[index.row()].name.c_str() ); - case 1: - return Variant( mInfo[index.row()].url.getFSPath() ); + case 1: { + if ( mDisplayLine ) { + std::string detail( !mInfo[index.row()].detail.empty() + ? mInfo[index.row()].detail + " (" + + mInfo[index.row()].range.toString() + ")" + : mInfo[index.row()].range.toString() ); + return Variant( detail ); + } else { + return Variant( mInfo[index.row()].url.getFSPath() ); + } + } } } else if ( role == ModelRole::Icon && index.column() == 0 ) { return mUISceneNode->findIcon( @@ -40,7 +50,7 @@ class LSPSymbolInfoModel : public Model { void update() override { onModelUpdate(); } - const std::vector& getInfo() const { return mInfo; } + const LSPSymbolInformationList& getInfo() const { return mInfo; } LSPSymbolInformation at( const size_t& idx ) { eeASSERT( idx < mInfo.size() ); @@ -49,7 +59,7 @@ class LSPSymbolInfoModel : public Model { const std::string& getQuery() const { return mQuery; } - void append( const std::vector& res ) { + void append( const LSPSymbolInformationList& res ) { mInfo.insert( mInfo.end(), res.begin(), res.end() ); std::sort( mInfo.begin(), mInfo.end(), []( const LSPSymbolInformation& l, const LSPSymbolInformation& r ) { @@ -60,8 +70,8 @@ class LSPSymbolInfoModel : public Model { void setQuery( const std::string& query ) { if ( mQuery != query ) { - mQuery = query; clear(); + mQuery = query; onModelUpdate(); } } @@ -71,9 +81,30 @@ class LSPSymbolInfoModel : public Model { protected: UISceneNode* mUISceneNode{ nullptr }; std::string mQuery; - std::vector mInfo; + LSPSymbolInformationList mInfo; + bool mDisplayLine{ false }; }; +LSPSymbolInformationList fuzzyMatchTextDocumentSymbol( const LSPSymbolInformationList& list, + const std::string& query, + const size_t& limit ) { + LSPSymbolInformationList nl; + std::map> matchesMap; + + for ( const auto& l : list ) { + int matchName = String::fuzzyMatch( l.name, query ); + matchesMap.insert( { matchName, l } ); + } + + while ( matchesMap.size() > limit ) + matchesMap.erase( std::prev( matchesMap.end() ) ); + + for ( const auto& m : matchesMap ) + nl.emplace_back( std::move( m.second ) ); + + return nl; +} + static int LOCATEBAR_MAX_VISIBLE_ITEMS = 18; static int LOCATEBAR_MAX_RESULTS = 100; @@ -169,7 +200,8 @@ void UniversalLocator::goToLine() { } static bool isCommand( const std::string& filename ) { - return !filename.empty() && ( filename == "> " || filename == ": " || filename == "l " ); + return !filename.empty() && + ( filename == "> " || filename == ": " || filename == "l " || filename == ". " ); } void UniversalLocator::initLocateBar( UILocateBar* locateBar, UITextInput* locateInput ) { @@ -202,6 +234,8 @@ void UniversalLocator::initLocateBar( UILocateBar* locateBar, UITextInput* locat showCommandPalette(); } else if ( !txt.empty() && mLocateInput->getText()[0] == ':' ) { showWorkspaceSymbol(); + } else if ( String::startsWith( txt, ". " ) ) { + showDocumentSymbol(); } else { showLocateTable(); if ( !mApp->isDirTreeReady() ) @@ -259,7 +293,7 @@ void UniversalLocator::initLocateBar( UILocateBar* locateBar, UITextInput* locat Variant vPath( modelEvent->getModel()->data( modelEvent->getModel()->index( modelEvent->getModelIndex().row(), 1 ), ModelRole::Display ) ); - if ( vPath.isValid() ) { + if ( vPath.isValid() && !String::startsWith( mLocateInput->getText(), ". " ) ) { std::string path( vPath.is( Variant::Type::cstr ) ? vPath.asCStr() : vPath.asStdString() ); if ( path.empty() ) @@ -288,6 +322,22 @@ void UniversalLocator::initLocateBar( UILocateBar* locateBar, UITextInput* locat } } mLocateBarLayout->execute( "close-locatebar" ); + } else { + Variant rangeStr( modelEvent->getModel()->data( + modelEvent->getModel()->index( modelEvent->getModelIndex().row(), 1 ), + ModelRole::Custom ) ); + auto range = rangeStr.isValid() + ? TextRange::fromString( rangeStr.asStdString() ) + : TextRange(); + if ( !range.isValid() ) + return; + UITab* tab = mSplitter->isDocumentOpen( URI( mCurDocURI ), true ); + if ( tab ) { + tab->getTabWidget()->setTabSelected( tab ); + UICodeEditor* editor = tab->getOwnedWidget()->asType(); + editor->goToLine( range.start() ); + mLocateBarLayout->execute( "close-locatebar" ); + } } } } @@ -370,21 +420,42 @@ void UniversalLocator::showWorkspaceSymbol() { updateLocateBar(); } +void UniversalLocator::showDocumentSymbol() { + showBar(); + + if ( mLocateInput->getText().empty() || mLocateInput->getText()[0] != '.' ) + mLocateInput->setText( ". " ); + + requestDocumentSymbol(); + updateLocateBar(); +} + +void UniversalLocator::onCodeEditorFocusChange( UICodeEditor* editor ) { + if ( !mLocateTable || !mLocateTable->isVisible() ) + return; + + if ( String::startsWith( mLocateInput->getText(), ". " ) && + editor->getDocument().getURI().toString() != mCurDocURI ) + showDocumentSymbol(); +} + +std::shared_ptr UniversalLocator::emptyModel( const String& defTxt, + const std::string& query ) { + LSPSymbolInformation info; + info.name = defTxt.toUtf8(); + info.url = ""; + return LSPSymbolInfoModel::create( mUISceneNode, query, { info } ); +} + void UniversalLocator::requestWorkspaceSymbol() { if ( mLocateInput->getText().empty() ) return; auto txt( mLocateInput->getText().substr( 1 ).trim() ); if ( mWorkspaceSymbolQuery != txt.toUtf8() || mWorkspaceSymbolQuery.empty() ) { mWorkspaceSymbolQuery = txt.toUtf8(); - if ( mWorkspaceSymbolModel ) { - mWorkspaceSymbolModel->setQuery( mWorkspaceSymbolQuery ); - } else { + if ( !mWorkspaceSymbolModel ) { auto defTxt = mUISceneNode->i18n( "no_running_lsp_server", "No running LSP server" ); - LSPSymbolInformation info; - info.name = defTxt.toUtf8(); - info.url = ""; - mWorkspaceSymbolModel = LSPSymbolInfoModel::create( mApp->getUISceneNode(), - mWorkspaceSymbolQuery, { info } ); + mWorkspaceSymbolModel = emptyModel( defTxt, mWorkspaceSymbolQuery ); } mLocateTable->setModel( mWorkspaceSymbolModel ); @@ -394,26 +465,119 @@ void UniversalLocator::requestWorkspaceSymbol() { } } -void UniversalLocator::updateWorkspaceSymbol( const std::vector& res ) { -#if EE_PLATFORM != EE_PLATFORM_EMSCRIPTEN || defined( __EMSCRIPTEN_PTHREADS__ ) +void UniversalLocator::updateWorkspaceSymbol( const LSPSymbolInformationList& res ) { mUISceneNode->runOnMainThread( [&, res] { if ( !mWorkspaceSymbolModel ) { mWorkspaceSymbolModel = LSPSymbolInfoModel::create( mApp->getUISceneNode(), mWorkspaceSymbolQuery, res ); } else { + mWorkspaceSymbolModel->setQuery( mWorkspaceSymbolQuery ); mWorkspaceSymbolModel->append( res ); } mLocateTable->setModel( mWorkspaceSymbolModel ); mLocateTable->getSelection().set( mLocateTable->getModel()->index( 0 ) ); mLocateTable->scrollToTop(); } ); +} + +void UniversalLocator::requestDocumentSymbol() { + if ( mLocateInput->getText().empty() ) + return; + auto txt( mLocateInput->getText().substr( 1 ).trim() ); + bool needsUpdate = false; + if ( txt != mCurDocQuery || getCurDocURI() != mCurDocURI ) { + mCurDocQuery = txt; + mCurDocURI = getCurDocURI(); + needsUpdate = true; + } + + mLocateTable->setModel( mTextDocumentSymbolModel ); + + if ( mSplitter->curEditorIsNotNull() ) { + if ( needsUpdate ) { + mTextDocumentSymbolModel = emptyModel( + mUISceneNode->i18n( "no_running_lsp_server", "No running LSP server" ) ); + json j; + j["uri"] = mCurDocURI = getCurDocURI(); + mApp->getPluginManager()->sendRequest( PluginMessageType::TextDocumentFlattenSymbol, + PluginMessageFormat::JSON, &j ); + } else { + if ( !mTextDocumentSymbolModel ) { + mTextDocumentSymbolModel = emptyModel( + mUISceneNode->i18n( "no_running_lsp_server", "No running LSP server" ) ); + } + mLocateTable->setModel( mTextDocumentSymbolModel ); + } + } else { + mTextDocumentSymbolModel = + emptyModel( mUISceneNode->i18n( "no_open_document", "No open document" ) ); + mLocateTable->setModel( mTextDocumentSymbolModel ); + } +} + +void UniversalLocator::updateDocumentSymbol( const LSPSymbolInformationList& res ) { +#if EE_PLATFORM != EE_PLATFORM_EMSCRIPTEN || defined( __EMSCRIPTEN_PTHREADS__ ) + if ( mCurDocQuery.empty() ) { + mTextDocumentSymbolModel = + LSPSymbolInfoModel::create( mApp->getUISceneNode(), mCurDocQuery, res, true ); + mLocateTable->setModel( mTextDocumentSymbolModel ); + mLocateTable->getSelection().set( mLocateTable->getModel()->index( 0 ) ); + mLocateTable->scrollToTop(); + } else { + asyncFuzzyMatchTextDocumentSymbol( res, mCurDocQuery, 100, [&]( const auto model ) { + mTextDocumentSymbolModel = model; + mUISceneNode->runOnMainThread( [&] { + mLocateTable->setModel( mTextDocumentSymbolModel ); + mLocateTable->getSelection().set( mLocateTable->getModel()->index( 0 ) ); + mLocateTable->scrollToTop(); + } ); + } ); + } #else - mLocateTable->setModel( mWorkspaceSymbolModel ); - mLocateTable->getSelection().set( mLocateTable->getModel()->index( 0 ) ); - mLocateTable->scrollToTop(); + mUISceneNode->runOnMainThread( [&, res] { + if ( mCurDocQuery.empty() ) { + mTextDocumentSymbolModel = + LSPSymbolInfoModel::create( mApp->getUISceneNode(), mCurDocQuery, res, true ); + } else { + mTextDocumentSymbolModel = LSPSymbolInfoModel::create( + mApp->getUISceneNode(), mCurDocQuery, + fuzzyMatchTextDocumentSymbol( res, mCurDocQuery, 100 ), true ); + } + mLocateTable->setModel( mTextDocumentSymbolModel ); + mLocateTable->getSelection().set( mLocateTable->getModel()->index( 0 ) ); + mLocateTable->scrollToTop(); + } ); #endif } +std::string UniversalLocator::getCurDocURI() { + return mSplitter->curEditorIsNotNull() + ? mSplitter->getCurEditor()->getDocument().getURI().toString() + : ""; +} + +PluginRequestHandle UniversalLocator::processResponse( const PluginMessage& msg ) { + if ( msg.isResponse() && msg.type == PluginMessageType::WorkspaceSymbol && + msg.format == PluginMessageFormat::SymbolInformation ) { + updateWorkspaceSymbol( msg.asSymbolInformation() ); + } else if ( msg.isResponse() && msg.type == PluginMessageType::TextDocumentFlattenSymbol && + msg.format == PluginMessageFormat::SymbolInformation ) { + if ( String::startsWith( mLocateInput->getText(), ". " ) && msg.responseID.isString() && + mCurDocURI == msg.responseID.asString() ) + updateDocumentSymbol( msg.asSymbolInformation() ); + } + return {}; +} + +void UniversalLocator::asyncFuzzyMatchTextDocumentSymbol( + const LSPSymbolInformationList& list, const std::string& query, const size_t& limit, + std::function )> cb ) { + mApp->getThreadPool()->run( [this, list, query, limit, cb] { + cb( LSPSymbolInfoModel::create( + mUISceneNode, query, fuzzyMatchTextDocumentSymbol( list, query, limit ), true ) ); + } ); +} + std::vector UniversalLocator::getLocatorCommands() const { std::vector vec; UIIcon* icon = mUISceneNode->findIcon( "chevron-right" ); @@ -424,6 +588,10 @@ std::vector UniversalLocator::getLocatorComma { ": ", mUISceneNode->i18n( "search_for_workspace_symbols", "Search for Workspace Symbols" ), icon } ); + vec.push_back( { ". ", + mUISceneNode->i18n( "search_for_document_symbols", + "Search for Symbols in Current Document" ), + icon } ); vec.push_back( { "l ", mUISceneNode->i18n( "go_to_line_in_current_document", "Go To Line in Current Document" ), @@ -431,11 +599,4 @@ std::vector UniversalLocator::getLocatorComma return vec; } -PluginRequestHandle UniversalLocator::processResponse( const PluginMessage& msg ) { - if ( msg.isResponse() && msg.type == PluginMessageType::WorkspaceSymbol ) { - updateWorkspaceSymbol( msg.asSymbolInformation() ); - } - return {}; -} - } // namespace ecode diff --git a/src/tools/ecode/universallocator.hpp b/src/tools/ecode/universallocator.hpp index 1db7e7031..ace461099 100644 --- a/src/tools/ecode/universallocator.hpp +++ b/src/tools/ecode/universallocator.hpp @@ -34,6 +34,10 @@ class UniversalLocator { void showWorkspaceSymbol(); + void showDocumentSymbol(); + + void onCodeEditorFocusChange( UICodeEditor* editor ); + protected: UILocateBar* mLocateBarLayout{ nullptr }; UITableView* mLocateTable{ nullptr }; @@ -44,6 +48,9 @@ class UniversalLocator { std::string mWorkspaceSymbolQuery; App* mApp{ nullptr }; CommandPalette mCommandPalette; + std::string mCurDocURI; + std::string mCurDocQuery; + std::shared_ptr mTextDocumentSymbolModel{ nullptr }; void updateLocateBar(); @@ -53,9 +60,22 @@ class UniversalLocator { void requestWorkspaceSymbol(); - void updateWorkspaceSymbol( const std::vector& info ); + void updateWorkspaceSymbol( const LSPSymbolInformationList& info ); + + void requestDocumentSymbol(); + + void updateDocumentSymbol( const LSPSymbolInformationList& info ); + + std::string getCurDocURI(); std::vector getLocatorCommands() const; + + std::shared_ptr emptyModel( const String& defTxt, + const std::string& query = "" ); + + void asyncFuzzyMatchTextDocumentSymbol( + const LSPSymbolInformationList& list, const std::string& query, const size_t& limit, + std::function )> cb ); }; } // namespace ecode