diff --git a/src/tools/ecode/plugins/autocomplete/autocompleteplugin.cpp b/src/tools/ecode/plugins/autocomplete/autocompleteplugin.cpp index fa7c5e01d..e9935e01b 100644 --- a/src/tools/ecode/plugins/autocomplete/autocompleteplugin.cpp +++ b/src/tools/ecode/plugins/autocomplete/autocompleteplugin.cpp @@ -1,11 +1,14 @@ #include "autocompleteplugin.hpp" +#include #include #include #include #include #include +#include using namespace EE::Graphics; using namespace EE::System; +using json = nlohmann::json; namespace ecode { @@ -20,10 +23,15 @@ UICodeEditorPlugin* AutoCompletePlugin::New( const PluginManager* pluginManager } AutoCompletePlugin::AutoCompletePlugin( const PluginManager* pluginManager ) : + mManager( pluginManager ), mSymbolPattern( "[%a_ñàáâãäåèéêëìíîïòóôõöùúûüýÿÑÀÁÂÃÄÅÈÉÊËÌÍÎÏÒÓÔÕÖÙÚÛÜÝ][%w_" "ñàáâãäåèéêëìíîïòóôõöùúûüýÿÑÀÁÂÃÄÅÈÉÊËÌÍÎÏÒÓÔÕÖÙÚÛÜÝ]*" ), mBoxPadding( PixelDensity::dpToPx( Rectf( 4, 4, 4, 4 ) ) ), - mPool( pluginManager->getThreadPool() ) {} + mPool( pluginManager->getThreadPool() ) { + mManager->subscribeMessages( this, [&]( const PluginMessage& msg ) -> PluginRequestHandle { + return processResponse( msg ); + } ); +} AutoCompletePlugin::~AutoCompletePlugin() { mClosing = true; @@ -40,8 +48,11 @@ AutoCompletePlugin::~AutoCompletePlugin() { void AutoCompletePlugin::onRegister( UICodeEditor* editor ) { Lock l( mDocMutex ); std::vector listeners; - listeners.push_back( editor->addEventListener( Event::OnDocumentLoaded, - [&]( const Event* ) { mDirty = true; } ) ); + listeners.push_back( + editor->addEventListener( Event::OnDocumentLoaded, [&, editor]( const Event* ) { + mDirty = true; + tryRequestCapabilities( editor ); + } ) ); listeners.push_back( editor->addEventListener( Event::OnDocumentClosed, [&]( const Event* event ) { @@ -170,8 +181,23 @@ bool AutoCompletePlugin::onKeyDown( UICodeEditor* editor, const KeyEvent& event return false; } -bool AutoCompletePlugin::onTextInput( UICodeEditor* editor, const TextInputEvent& ) { +bool AutoCompletePlugin::onTextInput( UICodeEditor* editor, const TextInputEvent& event ) { std::string partialSymbol( getPartialSymbol( &editor->getDocument() ) ); + + auto lang = editor->getDocumentRef()->getSyntaxDefinition().getLSPName(); + auto cap = mCapabilities.find( lang ); + if ( cap != mCapabilities.end() ) { + const auto& triggerCharacters = cap->second.completionProvider.triggerCharacters; + if ( partialSymbol.size() >= 1 || + std::find( triggerCharacters.begin(), triggerCharacters.end(), event.getChar() ) != + triggerCharacters.end() ) { + updateSuggestions( partialSymbol, editor ); + } else { + resetSuggestions( editor ); + } + return false; + } + if ( partialSymbol.size() >= 3 ) { updateSuggestions( partialSymbol, editor ); } else { @@ -225,6 +251,43 @@ void AutoCompletePlugin::pickSuggestion( UICodeEditor* editor ) { resetSuggestions( editor ); } +PluginRequestHandle AutoCompletePlugin::processResponse( const PluginMessage& msg ) { + if ( msg.isResponse() && msg.type == PluginMessageType::CodeCompletion ) { + auto completion = msg.asCodeCompletion(); + std::vector suggestions; + for ( const auto& item : completion ) { + if ( !item.textEdit.text.empty() ) + suggestions.push_back( item.textEdit.text ); + else if ( !item.label.empty() ) + suggestions.push_back( item.label ); + else + suggestions.push_back( item.filterText ); + } + if ( suggestions.empty() ) + return {}; + Lock l( mSuggestionsMutex ); + mSuggestions = suggestions; + } else if ( msg.isBroadcast() && msg.type == PluginMessageType::LanguageServerCapabilities ) { + if ( msg.asLanguageServerCapabilities().ready ) { + LSPServerCapabilities cap = msg.asLanguageServerCapabilities(); + Lock l( mCapabilitiesMutex ); + mCapabilities[cap.language] = std::move( cap ); + } + } + return {}; +} + +void AutoCompletePlugin::tryRequestCapabilities( UICodeEditor* editor ) { + const auto& language = editor->getDocumentRef()->getSyntaxDefinition().getLSPName(); + auto it = mCapabilities.find( language ); + if ( it != mCapabilities.end() ) + return; + json data; + data["language"] = language; + mManager->sendRequest( this, PluginMessageType::LanguageServerCapabilities, + PluginMessageFormat::JSON, &data ); +} + std::string AutoCompletePlugin::getPartialSymbol( TextDocument* doc ) { TextPosition end = doc->getSelection().end(); TextPosition start = doc->startOfWord( end ); @@ -440,6 +503,16 @@ static std::vector fuzzyMatchSymbols( const AutoCompletePlugin::Sym void AutoCompletePlugin::runUpdateSuggestions( const std::string& symbol, const SymbolsList& symbols, UICodeEditor* editor ) { { + tryRequestCapabilities( editor ); + json data; + auto doc = editor->getDocumentRef(); + auto sel = doc->getSelection(); + data["uri"] = doc->getURI().toString(); + data["position"] = { { "line", sel.start().line() }, + { "character", sel.start().column() } }; + mManager->sendRequest( this, PluginMessageType::CodeCompletion, PluginMessageFormat::JSON, + &data ); + Lock l( mLangSymbolsMutex ); Lock l2( mSuggestionsMutex ); mSuggestions = fuzzyMatchSymbols( symbols, symbol, mSuggestionsMaxVisible ); diff --git a/src/tools/ecode/plugins/autocomplete/autocompleteplugin.hpp b/src/tools/ecode/plugins/autocomplete/autocompleteplugin.hpp index c8b7ccbd7..a90f24cf8 100644 --- a/src/tools/ecode/plugins/autocomplete/autocompleteplugin.hpp +++ b/src/tools/ecode/plugins/autocomplete/autocompleteplugin.hpp @@ -1,6 +1,7 @@ #ifndef ECODE_AUTOCOMPLETEPLUGIN_HPP #define ECODE_AUTOCOMPLETEPLUGIN_HPP +#include "../lsp/lspprotocol.hpp" #include "../pluginmanager.hpp" #include #include @@ -73,6 +74,7 @@ class AutoCompletePlugin : public UICodeEditorPlugin { void setDirty( bool dirty ); protected: + const PluginManager* mManager{ nullptr }; std::string mSymbolPattern; Rectf mBoxPadding; std::shared_ptr mPool; @@ -99,6 +101,8 @@ class AutoCompletePlugin : public UICodeEditorPlugin { std::vector mSuggestions; Uint32 mSuggestionsMaxVisible{ 8 }; UICodeEditor* mSuggestionsEditor{ nullptr }; + std::map mCapabilities; + Mutex mCapabilitiesMutex; Float mRowHeight{ 0 }; Rectf mBoxRect; @@ -121,6 +125,10 @@ class AutoCompletePlugin : public UICodeEditorPlugin { void updateLangCache( const std::string& langName ); void pickSuggestion( UICodeEditor* editor ); + + PluginRequestHandle processResponse( const PluginMessage& msg ); + + void tryRequestCapabilities( UICodeEditor* editor ); }; } // namespace ecode diff --git a/src/tools/ecode/plugins/linter/linterplugin.cpp b/src/tools/ecode/plugins/linter/linterplugin.cpp index 842588322..e5118a004 100644 --- a/src/tools/ecode/plugins/linter/linterplugin.cpp +++ b/src/tools/ecode/plugins/linter/linterplugin.cpp @@ -110,6 +110,22 @@ void LinterPlugin::loadLinterConfig( const std::string& path ) { e.what() ); } } + if ( config.contains( "disable_languages" ) && config["disable_languages"].is_array() ) { + const auto& langs = config["disable_languages"]; + try { + mLanguagesDisabled.clear(); + for ( const auto& lang : langs ) { + if ( lang.is_string() ) { + std::string lg = lang.get(); + if ( !lg.empty() ) + mLanguagesDisabled.insert( lg ); + } + } + } catch ( const json::exception& e ) { + Log::debug( "LinterPlugin::loadLinterConfig: Error parsing disable_languages: %s", + e.what() ); + } + } } if ( !j.contains( "linters" ) ) @@ -404,6 +420,11 @@ void LinterPlugin::setEnableLSPDiagnostics( bool enableLSPDiagnostics ) { } void LinterPlugin::lintDoc( std::shared_ptr doc ) { + if ( !mLanguagesDisabled.empty() && + mLanguagesDisabled.find( doc->getSyntaxDefinition().getLSPName() ) != + mLanguagesDisabled.end() ) + return; + ScopedOp op( [&]() { mWorkMutex.lock(); @@ -627,7 +648,7 @@ void LinterPlugin::drawAfterLineText( UICodeEditor* editor, const Int64& index, auto& match = matches[i]; bool isDuplicate = false; - if ( i > 1 ) { + if ( i > 0 ) { for ( size_t p = 0; p < i; ++p ) { if ( matches[p].range == match.range ) { isDuplicate = true; diff --git a/src/tools/ecode/plugins/linter/linterplugin.hpp b/src/tools/ecode/plugins/linter/linterplugin.hpp index 685e43a11..d89af8fd0 100644 --- a/src/tools/ecode/plugins/linter/linterplugin.hpp +++ b/src/tools/ecode/plugins/linter/linterplugin.hpp @@ -116,6 +116,7 @@ class LinterPlugin : public UICodeEditorPlugin { bool mShuttingDown{ false }; bool mHoveringMatch{ false }; bool mEnableLSPDiagnostics{ true }; + std::set mLanguagesDisabled; std::set mLSPLanguagesDisabled; LinterPlugin( const PluginManager* pluginManager ); diff --git a/src/tools/ecode/plugins/lsp/lspclientplugin.cpp b/src/tools/ecode/plugins/lsp/lspclientplugin.cpp index f9e00d010..9df15a675 100644 --- a/src/tools/ecode/plugins/lsp/lspclientplugin.cpp +++ b/src/tools/ecode/plugins/lsp/lspclientplugin.cpp @@ -23,9 +23,6 @@ LSPClientPlugin::LSPClientPlugin( const PluginManager* pluginManager ) : } LSPClientPlugin::~LSPClientPlugin() { - mManager->sendResponse( this, PluginMessageType::LSPClientPlugin, - PluginMessageFormat::LSPClientPlugin, nullptr, -1 ); - mClosing = true; Lock l( mDocMutex ); for ( const auto& editor : mEditors ) { @@ -88,11 +85,19 @@ PluginRequestHandle LSPClientPlugin::processMessage( const PluginMessage& msg ) return ret; break; } - case PluginMessageType::LSPClientPlugin: { - if ( msg.isRequest() ) { - mManager->sendResponse( this, PluginMessageType::LSPClientPlugin, - PluginMessageFormat::LSPClientPlugin, this, -1 ); - return PluginRequestHandle::broadcast(); + case PluginMessageType::LanguageServerCapabilities: { + if ( msg.isRequest() && msg.isJSON() ) { + const auto& data = msg.asJSON(); + if ( !data.contains( "language" ) ) { + return {}; + } + auto server = mClientManager.getOneLSPClientServer( msg.asJSON()["language"] ); + if ( server ) { + mManager->sendBroadcast( this, PluginMessageType::LanguageServerCapabilities, + PluginMessageFormat::LanguageServerCapabilities, + &server->getCapabilities() ); + return PluginRequestHandle::broadcast(); + } } break; } @@ -137,12 +142,8 @@ void LSPClientPlugin::load( const PluginManager* pluginManager ) { if ( mDocs.find( doc.first ) != mDocs.end() ) mClientManager.tryRunServer( doc.second ); mDelayedDocs.clear(); - if ( mReady ) { + if ( mReady ) fireReadyCbs(); - - mManager->sendResponse( this, PluginMessageType::LSPClientPlugin, - PluginMessageFormat::LSPClientPlugin, this, -1 ); - } } void LSPClientPlugin::loadLSPConfig( std::vector& lsps, const std::string& path ) { diff --git a/src/tools/ecode/plugins/lsp/lspclientserver.cpp b/src/tools/ecode/plugins/lsp/lspclientserver.cpp index bea79e113..4c7b53dfe 100644 --- a/src/tools/ecode/plugins/lsp/lspclientserver.cpp +++ b/src/tools/ecode/plugins/lsp/lspclientserver.cpp @@ -790,7 +790,7 @@ void LSPClientServer::initialize() { mLSP.name.c_str(), e.what() ); } #endif - + mCapabilities.language = mLSP.language; mReady = true; write( newRequest( "initialized" ) ); sendQueuedMessages(); @@ -960,18 +960,18 @@ LSPClientServer::didChange( TextDocument* doc, const std::vectorisDirty() ) { - TextDocument* doc = client.first; - LSPDocumentClient* docClient = client.second.get(); - mManager->getThreadPool()->run( [this, doc, docClient]() { - didChange( doc, docClient->getDocChange() ); - docClient->clearDocChange(); - } ); - client.second->resetDirty(); - } +void LSPClientServer::queueDidChange( const URI& document, int version, const std::string&, + const std::vector& change ) { + Lock l( mDidChangeMutex ); + mDidChangeQueue.push( { document, version, change } ); +} + +void LSPClientServer::processDidChangeQueue() { + Lock l( mDidChangeMutex ); + while ( !mDidChangeQueue.empty() ) { + auto& change = mDidChangeQueue.front(); + didChange( change.uri, change.version, "", change.change ); + mDidChangeQueue.pop(); } } diff --git a/src/tools/ecode/plugins/lsp/lspclientserver.hpp b/src/tools/ecode/plugins/lsp/lspclientserver.hpp index 6324cdb3b..08af0d54a 100644 --- a/src/tools/ecode/plugins/lsp/lspclientserver.hpp +++ b/src/tools/ecode/plugins/lsp/lspclientserver.hpp @@ -12,6 +12,7 @@ #include #include #include +#include using json = nlohmann::json; @@ -96,6 +97,11 @@ class LSPClientServer { LSPRequestHandle didChange( TextDocument* doc, const std::vector& change = {} ); + void queueDidChange( const URI& document, int version, const std::string& text, + const std::vector& change = {} ); + + void processDidChangeQueue(); + LSPRequestHandle documentDefinition( const URI& document, const TextPosition& pos ); LSPRequestHandle documentDeclaration( const URI& document, const TextPosition& pos ); @@ -104,8 +110,6 @@ class LSPClientServer { LSPRequestHandle documentImplementation( const URI& document, const TextPosition& pos ); - void updateDirty(); - bool hasDocument( TextDocument* doc ) const; bool hasDocument( const URI& uri ) const; @@ -192,6 +196,14 @@ class LSPClientServer { std::string mReceiveErr; LSPServerCapabilities mCapabilities; + struct DidChangeQueue { + URI uri; + IdType version; + std::vector change; + }; + std::queue mDidChangeQueue; + Mutex mDidChangeMutex; + int mLastMsgId{ -1 }; 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 999d3ba61..0e52e6fd5 100644 --- a/src/tools/ecode/plugins/lsp/lspclientservermanager.cpp +++ b/src/tools/ecode/plugins/lsp/lspclientservermanager.cpp @@ -151,8 +151,6 @@ void LSPClientServerManager::updateDirty() { { Lock l( mClientsMutex ); for ( auto& server : mClients ) { - server.second->updateDirty(); - if ( !server.second->hasDocuments() ) mLSPsToClose.push_back( server.first ); } @@ -236,4 +234,25 @@ LSPClientServer* LSPClientServerManager::getOneLSPClientServer( const URI& uri ) return nullptr; } +LSPClientServer* LSPClientServerManager::getOneLSPClientServer( const std::string& language ) { + std::vector servers; + Lock l( mClientsMutex ); + for ( auto& server : mClients ) { + if ( server.second->getDefinition().language == language ) + return server.second.get(); + } + return nullptr; +} + +std::vector +LSPClientServerManager::getLSPClientServers( const std::string& language ) { + std::vector servers; + Lock l( mClientsMutex ); + for ( auto& server : mClients ) { + if ( server.second->getDefinition().language == language ) + servers.push_back( server.second.get() ); + } + return servers; +} + } // namespace ecode diff --git a/src/tools/ecode/plugins/lsp/lspclientservermanager.hpp b/src/tools/ecode/plugins/lsp/lspclientservermanager.hpp index afbabde59..1308ec199 100644 --- a/src/tools/ecode/plugins/lsp/lspclientservermanager.hpp +++ b/src/tools/ecode/plugins/lsp/lspclientservermanager.hpp @@ -43,12 +43,16 @@ class LSPClientServerManager { std::vector getLSPClientServers( const URI& uri ); + std::vector getLSPClientServers( const std::string& language ); + LSPClientServer* getOneLSPClientServer( UICodeEditor* editor ); LSPClientServer* getOneLSPClientServer( const std::shared_ptr& doc ); LSPClientServer* getOneLSPClientServer( const URI& uri ); + LSPClientServer* getOneLSPClientServer( const std::string& language ); + void getAndGoToLocation( const std::shared_ptr& doc, const std::string& search ); const PluginManager* getPluginManager() const; diff --git a/src/tools/ecode/plugins/lsp/lspdocumentclient.cpp b/src/tools/ecode/plugins/lsp/lspdocumentclient.cpp index af4e021c3..5b0516b68 100644 --- a/src/tools/ecode/plugins/lsp/lspdocumentclient.cpp +++ b/src/tools/ecode/plugins/lsp/lspdocumentclient.cpp @@ -16,11 +16,11 @@ LSPDocumentClient::LSPDocumentClient( LSPClientServer* server, TextDocument* doc LSPDocumentClient::~LSPDocumentClient() {} void LSPDocumentClient::onDocumentTextChanged( const DocumentContentChange& change ) { - mModified = true; ++mVersion; - if ( !change.text.empty() || change.range.start() != change.range.end() ) - mDocChange.push_back( change ); - mLastModified.restart(); + // If several change event are being fired, the thread pool can't guaranteed that it will be + // 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(); } ); } void LSPDocumentClient::onDocumentUndoRedo( const TextDocument::UndoRedo& /*eventType*/ ) {} @@ -39,21 +39,13 @@ void LSPDocumentClient::onDocumentSaved( TextDocument* ) { } void LSPDocumentClient::onDocumentClosed( TextDocument* ) { - mServer->didClose( mDoc ); + mServer->getThreadPool()->run( [&]() { mServer->didClose( mDoc ); } ); } 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; } @@ -66,14 +58,6 @@ int LSPDocumentClient::getVersion() const { return mVersion; } -const std::vector& LSPDocumentClient::getDocChange() const { - return mDocChange; -} - -void LSPDocumentClient::clearDocChange() { - mDocChange.clear(); -} - void LSPDocumentClient::notifyOpen() { eeASSERT( mDoc ); mServer->didOpen( mDoc, ++mVersion ); diff --git a/src/tools/ecode/plugins/lsp/lspdocumentclient.hpp b/src/tools/ecode/plugins/lsp/lspdocumentclient.hpp index 24b4c4fe2..be633b217 100644 --- a/src/tools/ecode/plugins/lsp/lspdocumentclient.hpp +++ b/src/tools/ecode/plugins/lsp/lspdocumentclient.hpp @@ -1,6 +1,7 @@ #ifndef ECODE_LSPDOCUMENTCLIENT_HPP #define ECODE_LSPDOCUMENTCLIENT_HPP +#include "../pluginmanager.hpp" #include #include @@ -29,28 +30,18 @@ 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; - const std::vector& getDocChange() const; - - void clearDocChange(); protected: LSPClientServer* mServer{ nullptr }; TextDocument* mDoc{ nullptr }; - bool mModified{ false }; - int mVersion{ 0 }; - Clock mLastModified; - std::vector mDocChange; + PluginIDType mVersion{ 0 }; }; } // namespace ecode diff --git a/src/tools/ecode/plugins/lsp/lspprotocol.hpp b/src/tools/ecode/plugins/lsp/lspprotocol.hpp index e863de6d4..41ffd7800 100644 --- a/src/tools/ecode/plugins/lsp/lspprotocol.hpp +++ b/src/tools/ecode/plugins/lsp/lspprotocol.hpp @@ -81,6 +81,7 @@ struct LSPWorkspaceFoldersServerCapabilities { struct LSPServerCapabilities { bool ready = false; + std::string language; LSPTextDocumentSyncOptions textDocumentSync; bool hoverProvider = false; LSPCompletionOptions completionProvider; diff --git a/src/tools/ecode/plugins/pluginmanager.cpp b/src/tools/ecode/plugins/pluginmanager.cpp index b58f4e788..09bb90cd8 100644 --- a/src/tools/ecode/plugins/pluginmanager.cpp +++ b/src/tools/ecode/plugins/pluginmanager.cpp @@ -135,7 +135,7 @@ void PluginManager::sendResponse( UICodeEditorPlugin* pluginWho, PluginMessageTy } void PluginManager::sendBroadcast( UICodeEditorPlugin* pluginWho, PluginMessageType type, - PluginMessageFormat format, void* data ) const { + PluginMessageFormat format, const void* data ) const { if ( mClosing ) return; for ( const auto& plugin : mSubscribedPlugins ) diff --git a/src/tools/ecode/plugins/pluginmanager.hpp b/src/tools/ecode/plugins/pluginmanager.hpp index f52270625..3f0aefc06 100644 --- a/src/tools/ecode/plugins/pluginmanager.hpp +++ b/src/tools/ecode/plugins/pluginmanager.hpp @@ -59,13 +59,14 @@ struct PluginDefinition { enum class PluginMessageType { WorkspaceFolderChanged, // Broadcast the workspace folder from the application to the plugins Diagnostics, // Broadcast a document diagnostics from the LSP Client - CodeCompletion, // Request the LSP Client to start a code completion in the requested document - // and position - LSPClientPlugin, // Request and/or Broadcast the LSP Client + CodeCompletion, // Request the LSP Client to start a code completion in the requested document + // and position + LanguageServerCapabilities, // Request the language server capabilities of a language if there + // is any available, it will be returned as a broadcast Undefined }; -enum class PluginMessageFormat { JSON, Diagnostics, CodeCompletion, LSPClientPlugin }; +enum class PluginMessageFormat { JSON, Diagnostics, CodeCompletion, LanguageServerCapabilities }; using PluginIDType = Int64; @@ -89,8 +90,8 @@ struct PluginMessage { return *static_cast*>( data ); } - const LSPClientPlugin* asLSPClientPlugin() const { - return static_cast( data ); + const LSPServerCapabilities& asLanguageServerCapabilities() const { + return *static_cast( data ); } bool isResponse() const { return -1 != responseID && 0 != responseID; } @@ -174,7 +175,7 @@ class PluginManager { const PluginIDType& responseID ) const; void sendBroadcast( UICodeEditorPlugin* pluginWho, PluginMessageType, PluginMessageFormat, - void* data ) const; + const void* data ) const; void sendBroadcast( const PluginMessageType& notification, const PluginMessageFormat& format, void* data );