diff --git a/include/eepp/system/process.hpp b/include/eepp/system/process.hpp index 9e351423f..01021b531 100644 --- a/include/eepp/system/process.hpp +++ b/include/eepp/system/process.hpp @@ -195,7 +195,7 @@ class EE_API Process { void* mProcess{ nullptr }; bool mShuttingDown{ false }; bool mIsAsync{ false }; - size_t mBufferSize{ 131072 }; + ssize_t mBufferSize{ 131072 }; std::thread mStdOutThread; std::thread mStdErrThread; Mutex mStdInMutex; diff --git a/include/eepp/system/threadpool.hpp b/include/eepp/system/threadpool.hpp index 0c1870b6f..803318e3e 100644 --- a/include/eepp/system/threadpool.hpp +++ b/include/eepp/system/threadpool.hpp @@ -27,7 +27,8 @@ class EE_API ThreadPool : NonCopyable { virtual ~ThreadPool(); - void run( const std::function& func, const std::function& doneCallback ); + void run( + const std::function& func, const std::function& doneCallback = []() {} ); Uint32 numThreads() const; diff --git a/src/eepp/system/process.cpp b/src/eepp/system/process.cpp index 8951b1ce7..4e478ee10 100644 --- a/src/eepp/system/process.cpp +++ b/src/eepp/system/process.cpp @@ -188,26 +188,32 @@ void Process::startAsyncRead( ReadFn readStdOut, ReadFn readStdErr ) { if ( stdOutFd ) { mStdOutThread = std::thread( [this, stdOutFd]() { DWORD n; - std::unique_ptr buffer( new char[mBufferSize] ); + std::string buffer; + buffer.resize( mBufferSize ); while ( !mShuttingDown ) { - BOOL bSuccess = ReadFile( stdOutFd, static_cast( buffer.get() ), + BOOL bSuccess = ReadFile( stdOutFd, static_cast( &buffer[0] ), static_cast( mBufferSize ), &n, nullptr ); if ( !bSuccess || n == 0 ) break; - mReadStdOutFn( buffer.get(), static_cast( n ) ); + if ( n < mBufferSize - 1 ) + buffer[n] = '\0'; + mReadStdOutFn( buffer.c_str(), static_cast( n ) ); } } ); } if ( stdErrFd ) { mStdErrThread = std::thread( [this, stdErrFd]() { DWORD n; - std::unique_ptr buffer( new char[mBufferSize] ); + std::string buffer; + buffer.resize( mBufferSize ); while ( !mShuttingDown ) { - BOOL bSuccess = ReadFile( stdErrFd, static_cast( buffer.get() ), + BOOL bSuccess = ReadFile( stdErrFd, static_cast( &buffer[0] ), static_cast( mBufferSize ), &n, nullptr ); if ( !bSuccess || n == 0 ) break; - mReadStdErrFn( buffer.get(), static_cast( n ) ); + if ( n < mBufferSize - 1 ) + buffer[n] = '\0'; + mReadStdErrFn( buffer.c_str(), static_cast( n ) ); } } ); } @@ -232,7 +238,8 @@ void Process::startAsyncRead( ReadFn readStdOut, ReadFn readStdErr ) { : -1; pollfds.back().events = POLLIN; } - auto buffer = std::unique_ptr( new char[mBufferSize] ); + std::string buffer; + buffer.resize( mBufferSize ); bool anyOpen = !pollfds.empty(); while ( anyOpen && !mShuttingDown && errno != EINTR ) { int res = poll( pollfds.data(), static_cast( pollfds.size() ), 100 ); @@ -241,12 +248,14 @@ void Process::startAsyncRead( ReadFn readStdOut, ReadFn readStdErr ) { for ( size_t i = 0; i < pollfds.size(); ++i ) { if ( pollfds[i].fd >= 0 ) { if ( pollfds[i].revents & POLLIN ) { - const ssize_t n = read( pollfds[i].fd, buffer.get(), mBufferSize ); + const ssize_t n = read( pollfds[i].fd, &buffer[0], mBufferSize ); if ( n > 0 ) { + if ( n < mBufferSize - 1 ) + buffer[n] = '\0'; if ( fdIsStdOut[i] ) - mReadStdOutFn( buffer.get(), static_cast( n ) ); + mReadStdOutFn( buffer.c_str(), static_cast( n ) ); else - mReadStdErrFn( buffer.get(), static_cast( n ) ); + mReadStdErrFn( buffer.c_str(), static_cast( n ) ); } else if ( n < 0 && errno != EINTR && errno != EAGAIN && errno != EWOULDBLOCK ) { pollfds[i].fd = -1; diff --git a/src/tools/ecode/ecode.cpp b/src/tools/ecode/ecode.cpp index e18d713e5..abd22548c 100644 --- a/src/tools/ecode/ecode.cpp +++ b/src/tools/ecode/ecode.cpp @@ -2,7 +2,7 @@ #include "plugins/autocomplete/autocompleteplugin.hpp" #include "plugins/formatter/formatterplugin.hpp" #include "plugins/linter/linterplugin.hpp" -//#include "plugins/lsp/lspclientplugin.hpp" +// #include "plugins/lsp/lspclientplugin.hpp" #include "version.hpp" #include #include @@ -369,7 +369,7 @@ void App::initPluginManager() { mPluginManager->registerPlugin( LinterPlugin::Definition() ); mPluginManager->registerPlugin( FormatterPlugin::Definition() ); mPluginManager->registerPlugin( AutoCompletePlugin::Definition() ); - //mPluginManager->registerPlugin( LSPClientPlugin::Definition() ); + // mPluginManager->registerPlugin( LSPClientPlugin::Definition() ); } void App::loadConfig( const LogLevel& logLevel ) { diff --git a/src/tools/ecode/plugins/lsp/lspclientplugin.cpp b/src/tools/ecode/plugins/lsp/lspclientplugin.cpp index a8f6dd3bd..65ddb995c 100644 --- a/src/tools/ecode/plugins/lsp/lspclientplugin.cpp +++ b/src/tools/ecode/plugins/lsp/lspclientplugin.cpp @@ -26,6 +26,10 @@ LSPClientPlugin::~LSPClientPlugin() { } } +void LSPClientPlugin::update( UICodeEditor* ) { + mClientManager.updateDirty(); +} + void LSPClientPlugin::load( const PluginManager* pluginManager ) { std::vector paths; std::string path( pluginManager->getResourcesPath() + "plugins/lspclient.json" ); diff --git a/src/tools/ecode/plugins/lsp/lspclientplugin.hpp b/src/tools/ecode/plugins/lsp/lspclientplugin.hpp index fa94c7630..f451e169b 100644 --- a/src/tools/ecode/plugins/lsp/lspclientplugin.hpp +++ b/src/tools/ecode/plugins/lsp/lspclientplugin.hpp @@ -33,6 +33,8 @@ class LSPClientPlugin : public UICodeEditorPlugin { virtual ~LSPClientPlugin(); + virtual void update( UICodeEditor* ); + std::string getId() { return Definition().id; } std::string getTitle() { return Definition().name; } diff --git a/src/tools/ecode/plugins/lsp/lspclientserver.cpp b/src/tools/ecode/plugins/lsp/lspclientserver.cpp index fc51106e2..48f63060b 100644 --- a/src/tools/ecode/plugins/lsp/lspclientserver.cpp +++ b/src/tools/ecode/plugins/lsp/lspclientserver.cpp @@ -1,4 +1,9 @@ #include "lspclientserver.hpp" +#include "lspclientservermanager.hpp" +#include +#include +#include +#include #include #include #include @@ -69,13 +74,14 @@ static json textDocumentParams( const URI& document, int version = -1 ) { return textDocumentParams( versionedTextDocumentIdentifier( document, version ) ); } -LSPClientServer::LSPClientServer( const LSPDefinition& lsp, const std::string& rootPath ) : - mLSP( lsp ), mRootPath( rootPath ) {} +LSPClientServer::LSPClientServer( LSPClientServerManager* manager, const String::HashType& id, + const LSPDefinition& lsp, const std::string& rootPath ) : + mManager( manager ), mId( id ), mLSP( lsp ), mRootPath( rootPath ) {} LSPClientServer::~LSPClientServer() { - for ( const auto& client : mClients ) { + Lock l( mClientsMutex ); + for ( const auto& client : mClients ) client.first->unregisterClient( client.second.get() ); - } } bool LSPClientServer::start() { @@ -91,8 +97,9 @@ bool LSPClientServer::start() { } bool LSPClientServer::registerDoc( const std::shared_ptr& doc ) { - for ( auto& cdoc : mDocs ) { - if ( cdoc.get() == doc.get() ) { + Lock l( mClientsMutex ); + for ( TextDocument* cdoc : mDocs ) { + if ( cdoc == doc.get() ) { if ( mClients.find( doc.get() ) == mClients.end() ) { mClients[doc.get()] = std::make_unique( this, doc.get() ); return true; @@ -102,11 +109,19 @@ bool LSPClientServer::registerDoc( const std::shared_ptr& doc ) { } mClients[doc.get()] = std::make_unique( this, doc.get() ); - mDocs.emplace_back( doc ); + mDocs.emplace_back( doc.get() ); doc->registerClient( mClients[doc.get()].get() ); return true; } +LSPClientServer::RequestHandle LSPClientServer::cancel( int reqid ) { + if ( mHandlers.erase( reqid ) > 0 ) { + auto params = json{ MEMBER_ID, reqid }; + return write( newRequest( "$/cancelRequest", params ) ); + } + return RequestHandle(); +} + LSPClientServer::RequestHandle LSPClientServer::write( const json& msg, const GenericReplyHandler& h, const GenericReplyHandler& eh, @@ -157,6 +172,85 @@ LSPClientServer::RequestHandle LSPClientServer::didOpen( const URI& document, return send( newRequest( "textDocument/didOpen", params ) ); } +LSPClientServer::RequestHandle LSPClientServer::didOpen( TextDocument* doc, int version ) { + if ( doc->isDirty() ) { + IOStreamString text; + doc->save( text, true ); + return didOpen( doc->getURI(), text.getStream(), version ); + } else { + std::string text; + FileSystem::fileGet( doc->getFilePath(), text ); + return didOpen( doc->getURI(), text, version ); + } +} + +LSPClientServer::RequestHandle LSPClientServer::didSave( const URI& document, + const std::string& text ) { + auto params = textDocumentParams( document ); + if ( !text.empty() ) + params["text"] = text; + return send( newRequest( "textDocument/didSave", params ) ); +} + +LSPClientServer::RequestHandle LSPClientServer::didSave( TextDocument* doc ) { + return didSave( doc->getURI(), doc->getText( doc->getDocRange() ).toUtf8() ); +} + +LSPClientServer::RequestHandle LSPClientServer::didChange( const URI& document, int version, + const std::string& text ) { + auto params = textDocumentParams( document, version ); + if ( !text.empty() ) + params["contentChanges"] = { json{ MEMBER_TEXT, text } }; + return send( newRequest( "textDocument/didChange", params ) ); +} + +LSPClientServer::RequestHandle LSPClientServer::didChange( TextDocument* doc ) { + Lock l( mClientsMutex ); + auto it = mClients.find( doc ); + if ( it != mClients.end() ) + return didChange( doc->getURI(), it->second->getVersion(), + doc->getText( doc->getDocRange() ).toUtf8() ); + return RequestHandle(); +} + +void LSPClientServer::updateDirty() { + Lock l( mClientsMutex ); + for ( auto& client : mClients ) { + if ( client.second->isDirty() ) { + TextDocument* doc = client.first; + mManager->getThreadPool()->run( [this, doc]() { didChange( doc ); } ); + client.second->resetDirty(); + } + } +} + +LSPClientServer::RequestHandle LSPClientServer::didClose( const URI& document ) { + auto params = textDocumentParams( document ); + return send( newRequest( "textDocument/didClose", params ) ); +} + +LSPClientServer::RequestHandle LSPClientServer::didClose( TextDocument* doc ) { + auto ret = didClose( doc->getURI() ); + Lock l( mClientsMutex ); + if ( mClients.erase( doc ) > 0 ) { + auto it = std::find( mDocs.begin(), mDocs.end(), doc ); + if ( it != mDocs.end() ) + mDocs.erase( it ); + // No more docs are being used, close the LSP + if ( mDocs.empty() ) + mManager->notifyClose( mId ); + } + return ret; +} + +LSPClientServerManager* LSPClientServer::getManager() const { + return mManager; +} + +const std::shared_ptr& LSPClientServer::getThreadPool() const { + return mManager->getThreadPool(); +} + LSPClientServer::RequestHandle LSPClientServer::documentSymbols( const URI& document, const GenericReplyHandler& h, const GenericReplyHandler& eh ) { diff --git a/src/tools/ecode/plugins/lsp/lspclientserver.hpp b/src/tools/ecode/plugins/lsp/lspclientserver.hpp index 949d292bd..52899a039 100644 --- a/src/tools/ecode/plugins/lsp/lspclientserver.hpp +++ b/src/tools/ecode/plugins/lsp/lspclientserver.hpp @@ -18,6 +18,8 @@ using namespace EE::UI::Doc; namespace ecode { +class LSPClientServerManager; + class LSPClientServer { public: template using ReplyHandler = std::function; @@ -37,7 +39,8 @@ class LSPClientServer { } }; - LSPClientServer( const LSPDefinition& lsp, const std::string& rootPath ); + LSPClientServer( LSPClientServerManager* manager, const String::HashType& id, + const LSPDefinition& lsp, const std::string& rootPath ); ~LSPClientServer(); @@ -45,27 +48,52 @@ class LSPClientServer { bool registerDoc( const std::shared_ptr& doc ); - int cancel( int id ) { return id; } + LSPClientServerManager* getManager() const; + + const std::shared_ptr& getThreadPool() const; + + LSPClientServer::RequestHandle cancel( int id ); RequestHandle send( const json& msg, const GenericReplyHandler& h = nullptr, const GenericReplyHandler& eh = nullptr ); - LSPClientServer::RequestHandle didOpen( const URI& document, const std::string& text, - int version ); - const LSPDefinition& getDefinition() const { return mLSP; } LSPClientServer::RequestHandle documentSymbols( const URI& document, const GenericReplyHandler& h, const GenericReplyHandler& eh ); + LSPClientServer::RequestHandle didOpen( const URI& document, const std::string& text, + int version ); + + LSPClientServer::RequestHandle didOpen( TextDocument* doc, int version ); + + LSPClientServer::RequestHandle didSave( const URI& document, const std::string& text ); + + LSPClientServer::RequestHandle didSave( TextDocument* doc ); + + LSPClientServer::RequestHandle didClose( const URI& document ); + + LSPClientServer::RequestHandle didClose( TextDocument* document ); + + LSPClientServer::RequestHandle didChange( const URI& document, int version, + const std::string& text ); + + LSPClientServer::RequestHandle didChange( TextDocument* doc ); + + void updateDirty(); + protected: + LSPClientServerManager* mManager{ nullptr }; + String::HashType mId; LSPDefinition mLSP; std::string mRootPath; Process mProcess; - std::vector> mDocs; + std::vector mDocs; std::map> mClients; std::map> mHandlers; + Mutex mClientsMutex; + int mLastMsgId{ 0 }; void readStdOut( const char* bytes, size_t n ); diff --git a/src/tools/ecode/plugins/lsp/lspclientservermanager.cpp b/src/tools/ecode/plugins/lsp/lspclientservermanager.cpp index c7f716671..01928dce0 100644 --- a/src/tools/ecode/plugins/lsp/lspclientservermanager.cpp +++ b/src/tools/ecode/plugins/lsp/lspclientservermanager.cpp @@ -39,9 +39,10 @@ LSPClientServerManager::supportsLSP( const std::shared_ptr& doc ) return lsps; } -std::shared_ptr -LSPClientServerManager::runLSPServer( const LSPDefinition& lsp, const std::string& rootPath ) { - auto server = std::make_shared( lsp, rootPath ); +std::unique_ptr +LSPClientServerManager::runLSPServer( const String::HashType& id, const LSPDefinition& lsp, + const std::string& rootPath ) { + auto server = std::make_unique( this, id, lsp, rootPath ); server->start(); return server; } @@ -76,17 +77,23 @@ void LSPClientServerManager::tryRunServer( const std::shared_ptr& auto lspName = lsp.name.empty() ? lsp.command : lsp.name; String::HashType id = String::hash( lspName + "|" + lsp.language + "|" + rootPath ); auto clientIt = mClients.find( id ); - std::shared_ptr server; + LSPClientServer* server = nullptr; if ( clientIt == mClients.end() ) { - server = runLSPServer( lsp, rootPath ); - if ( server.use_count() ) - mClients[id] = server; + std::unique_ptr serverUP = runLSPServer( id, lsp, rootPath ); + if ( ( server = serverUP.get() ) ) + mClients[id] = std::move( serverUP ); } else { - server = clientIt->second; + server = clientIt->second.get(); } - if ( server.use_count() ) { + if ( server ) server->registerDoc( doc ); - } + } +} + +void LSPClientServerManager::notifyClose( const String::HashType& id ) { + auto it = mClients.find( id ); + if ( it != mClients.end() ) { + mClients.erase( it ); } } @@ -98,4 +105,13 @@ size_t LSPClientServerManager::clientCount() const { return mClients.size(); } +const std::shared_ptr& LSPClientServerManager::getThreadPool() const { + return mPool; +} + +void LSPClientServerManager::updateDirty() { + for ( auto& server : mClients ) + server.second->updateDirty(); +} + } // namespace ecode diff --git a/src/tools/ecode/plugins/lsp/lspclientservermanager.hpp b/src/tools/ecode/plugins/lsp/lspclientservermanager.hpp index 3336b269d..f3701f478 100644 --- a/src/tools/ecode/plugins/lsp/lspclientservermanager.hpp +++ b/src/tools/ecode/plugins/lsp/lspclientservermanager.hpp @@ -20,19 +20,28 @@ class LSPClientServerManager { size_t clientCount() const; + const std::shared_ptr& getThreadPool() const; + + void updateDirty(); + protected: + friend class LSPClientServer; + std::shared_ptr mPool; - std::map> mClients; + std::map> mClients; std::vector mLSPs; std::vector supportsLSP( const std::shared_ptr& doc ); - std::shared_ptr runLSPServer( const LSPDefinition& lsp, + std::unique_ptr runLSPServer( const String::HashType& id, + const LSPDefinition& lsp, const std::string& rootPath ); std::string findRootPath( const LSPDefinition& lsp, const std::shared_ptr& doc ); void tryRunServer( const std::shared_ptr& doc ); + + void notifyClose( const String::HashType& id ); }; } // namespace ecode diff --git a/src/tools/ecode/plugins/lsp/lspdocumentclient.cpp b/src/tools/ecode/plugins/lsp/lspdocumentclient.cpp index 14e89eb6a..4166fafb6 100644 --- a/src/tools/ecode/plugins/lsp/lspdocumentclient.cpp +++ b/src/tools/ecode/plugins/lsp/lspdocumentclient.cpp @@ -10,17 +10,13 @@ namespace ecode { LSPDocumentClient::LSPDocumentClient( LSPClientServer* server, TextDocument* doc ) : mServer( server ), mDoc( doc ) { notifyOpen(); - mServer->documentSymbols( - mDoc->getURI(), - [&]( const json& ) { - - }, - [&]( const json& ) { - - } ); } -void LSPDocumentClient::onDocumentTextChanged() {} +void LSPDocumentClient::onDocumentTextChanged() { + mModified = true; + ++mVersion; + mLastModified.restart(); +} void LSPDocumentClient::onDocumentUndoRedo( const TextDocument::UndoRedo& /*eventType*/ ) {} @@ -33,24 +29,42 @@ void LSPDocumentClient::onDocumentLineCountChange( const size_t& /*lastCount*/, void LSPDocumentClient::onDocumentLineChanged( const Int64& /*lineIndex*/ ) {} -void LSPDocumentClient::onDocumentSaved( TextDocument* ) {} +void LSPDocumentClient::onDocumentSaved( TextDocument* ) { + mServer->getThreadPool()->run( [&]() { mServer->didSave( mDoc ); } ); +} -void LSPDocumentClient::onDocumentClosed( TextDocument* ) {} +void LSPDocumentClient::onDocumentClosed( TextDocument* ) { + mServer->didClose( mDoc ); + mDoc = nullptr; +} void LSPDocumentClient::onDocumentDirtyOnFileSystem( TextDocument* ) {} void LSPDocumentClient::onDocumentMoved( TextDocument* ) {} +bool LSPDocumentClient::isDirty() const { + return mModified && mLastModified.getElapsedTime() > Milliseconds( 250.f ); +} + +void LSPDocumentClient::resetDirty() { + mModified = false; +} + +TextDocument* LSPDocumentClient::getDoc() const { + return mDoc; +} + +LSPClientServer* LSPDocumentClient::getServer() const { + return mServer; +} + +int LSPDocumentClient::getVersion() const { + return mVersion; +} + void LSPDocumentClient::notifyOpen() { - if ( mDoc->isDirty() ) { - IOStreamString text; - mDoc->save( text, true ); - mServer->didOpen( mDoc->getURI(), text.getStream(), mVersion ); - } else { - std::string text; - FileSystem::fileGet( mDoc->getFilePath(), text ); - mServer->didOpen( mDoc->getURI(), text, mVersion ); - } + eeASSERT( mDoc ); + mServer->didOpen( mDoc, ++mVersion ); } } // namespace ecode diff --git a/src/tools/ecode/plugins/lsp/lspdocumentclient.hpp b/src/tools/ecode/plugins/lsp/lspdocumentclient.hpp index 16e4cbede..0d39ac405 100644 --- a/src/tools/ecode/plugins/lsp/lspdocumentclient.hpp +++ b/src/tools/ecode/plugins/lsp/lspdocumentclient.hpp @@ -1,9 +1,11 @@ #ifndef ECODE_LSPDOCUMENTCLIENT_HPP #define ECODE_LSPDOCUMENTCLIENT_HPP +#include #include using namespace EE; +using namespace EE::System; using namespace EE::UI::Doc; namespace ecode { @@ -25,11 +27,24 @@ class LSPDocumentClient : public TextDocument::Client { virtual void onDocumentDirtyOnFileSystem( TextDocument* ); virtual void onDocumentMoved( TextDocument* ); + bool isDirty() const; + void notifyOpen(); + + void resetDirty(); + + TextDocument* getDoc() const; + + LSPClientServer* getServer() const; + + int getVersion() const; + protected: LSPClientServer* mServer{ nullptr }; TextDocument* mDoc{ nullptr }; + bool mModified{ false }; int mVersion{ 0 }; + Clock mLastModified; }; } // namespace ecode