diff --git a/src/tools/ecode/plugins/linter/linterplugin.cpp b/src/tools/ecode/plugins/linter/linterplugin.cpp index 8be1ca580..d4b162fe9 100644 --- a/src/tools/ecode/plugins/linter/linterplugin.cpp +++ b/src/tools/ecode/plugins/linter/linterplugin.cpp @@ -249,18 +249,16 @@ void LinterPlugin::setMatches( TextDocument* doc, const MatchOrigin& origin, invalidateEditors( doc ); } -void LinterPlugin::processNotification( const PluginManager::Notification& notification ) { - if ( !mEnableLSPDiagnostics || - notification.type != PluginManager::NotificationType::PublishDiagnostics || - notification.format != PluginManager::NotificationFormat::Diagnostics ) - return; +PluginRequestHandle LinterPlugin::processMessage( const PluginMessage& notification ) { + if ( !mEnableLSPDiagnostics || notification.type != PluginMessageType::Diagnostics || + notification.format != PluginMessageFormat::Diagnostics ) + return PluginRequestHandle::empty(); const auto& diags = notification.asDiagnostics(); TextDocument* doc = getDocumentFromURI( diags.uri ); - if ( doc == nullptr ) - return; - if ( mLSPLanguagesDisabled.find( String::toLower( - doc->getSyntaxDefinition().getLSPName() ) ) != mLSPLanguagesDisabled.end() ) - return; + if ( doc == nullptr || + mLSPLanguagesDisabled.find( String::toLower( doc->getSyntaxDefinition().getLSPName() ) ) != + mLSPLanguagesDisabled.end() ) + return PluginRequestHandle::empty(); std::map> matches; @@ -275,6 +273,7 @@ void LinterPlugin::processNotification( const PluginManager::Notification& notif } setMatches( doc, MatchOrigin::Diagnostics, matches ); + return PluginRequestHandle::empty(); } TextDocument* LinterPlugin::getDocumentFromURI( const URI& uri ) { @@ -286,8 +285,9 @@ TextDocument* LinterPlugin::getDocumentFromURI( const URI& uri ) { } void LinterPlugin::load( const PluginManager* pluginManager ) { - pluginManager->subscribeNotifications( - this, [&]( const auto& notification ) { processNotification( notification ); } ); + pluginManager->subscribeMessages( this, [&]( const auto& notification ) -> PluginRequestHandle { + return processMessage( notification ); + } ); std::vector paths; std::string path( pluginManager->getResourcesPath() + "plugins/linters.json" ); if ( FileSystem::fileExists( path ) ) diff --git a/src/tools/ecode/plugins/linter/linterplugin.hpp b/src/tools/ecode/plugins/linter/linterplugin.hpp index feca37e36..d4ff62962 100644 --- a/src/tools/ecode/plugins/linter/linterplugin.hpp +++ b/src/tools/ecode/plugins/linter/linterplugin.hpp @@ -140,7 +140,7 @@ class LinterPlugin : public UICodeEditorPlugin { size_t linterFilePatternPosition( const std::vector& patterns ); - void processNotification( const PluginManager::Notification& notification ); + PluginRequestHandle processMessage( const PluginMessage& notification ); TextDocument* getDocumentFromURI( const URI& uri ); diff --git a/src/tools/ecode/plugins/lsp/lspclientplugin.cpp b/src/tools/ecode/plugins/lsp/lspclientplugin.cpp index 496cef7f1..f9e00d010 100644 --- a/src/tools/ecode/plugins/lsp/lspclientplugin.cpp +++ b/src/tools/ecode/plugins/lsp/lspclientplugin.cpp @@ -23,6 +23,9 @@ 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 ) { @@ -47,20 +50,62 @@ void LSPClientPlugin::update( UICodeEditor* ) { mClientManager.updateDirty(); } -void LSPClientPlugin::processNotification( const PluginManager::Notification& notification ) { - switch ( notification.type ) { - case PluginManager::WorkspaceFolderChanged: { - mClientManager.didChangeWorkspaceFolders( notification.asJSON()["folder"] ); +PluginRequestHandle LSPClientPlugin::processCodeCompletionRequest( const PluginMessage& msg ) { + if ( !msg.isRequest() || !msg.isJSON() ) + return {}; + + const auto& data = msg.asJSON(); + if ( !data.contains( "uri" ) || !data.contains( "position" ) ) + return {}; + + TextPosition position( LSPConverter::fromJSON( data["position"] ) ); + if ( !position.isValid() ) + return {}; + + URI uri( data["uri"] ); + auto server = mClientManager.getOneLSPClientServer( uri ); + if ( !server ) + return {}; + + auto ret = server->documentCompletion( + uri, position, [&]( const PluginIDType& id, const std::vector& items ) { + mManager->sendResponse( this, PluginMessageType::CodeCompletion, + PluginMessageFormat::CodeCompletion, &items, id ); + } ); + + return ret; +} + +PluginRequestHandle LSPClientPlugin::processMessage( const PluginMessage& msg ) { + switch ( msg.type ) { + case PluginMessageType::WorkspaceFolderChanged: { + mClientManager.didChangeWorkspaceFolders( msg.asJSON()["folder"] ); + break; + } + case PluginMessageType::CodeCompletion: { + auto ret = processCodeCompletionRequest( msg ); + if ( !ret.isEmpty() ) + return ret; + break; + } + case PluginMessageType::LSPClientPlugin: { + if ( msg.isRequest() ) { + mManager->sendResponse( this, PluginMessageType::LSPClientPlugin, + PluginMessageFormat::LSPClientPlugin, this, -1 ); + return PluginRequestHandle::broadcast(); + } break; } default: break; } + return PluginRequestHandle::empty(); } void LSPClientPlugin::load( const PluginManager* pluginManager ) { - pluginManager->subscribeNotifications( - this, [&]( const auto& notification ) { processNotification( notification ); } ); + pluginManager->subscribeMessages( this, [&]( const auto& notification ) -> PluginRequestHandle { + return processMessage( notification ); + } ); std::vector paths; std::string path( pluginManager->getResourcesPath() + "plugins/lspclient.json" ); if ( FileSystem::fileExists( path ) ) @@ -92,8 +137,12 @@ 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 ) { @@ -372,7 +421,7 @@ bool LSPClientPlugin::onMouseMove( UICodeEditor* editor, const Vector2i& positio return; server->documentHover( editor->getDocument().getURI(), currentMouseTextPosition( editor ), - [&, editor, position]( const LSPHover& resp ) { + [&, editor, position]( const Int64&, const LSPHover& resp ) { if ( resp.range.isValid() && !resp.contents.empty() ) { editor->runOnMainThread( [editor, resp, position, this]() { TextPosition startCursorPosition = @@ -425,4 +474,8 @@ void LSPClientPlugin::setHoverDelay( const Time& hoverDelay ) { mHoverDelay = hoverDelay; } +const LSPClientServerManager& LSPClientPlugin::getClientManager() const { + return mClientManager; +} + } // namespace ecode diff --git a/src/tools/ecode/plugins/lsp/lspclientplugin.hpp b/src/tools/ecode/plugins/lsp/lspclientplugin.hpp index 489843c06..8acafb3f3 100644 --- a/src/tools/ecode/plugins/lsp/lspclientplugin.hpp +++ b/src/tools/ecode/plugins/lsp/lspclientplugin.hpp @@ -64,6 +64,8 @@ class LSPClientPlugin : public UICodeEditorPlugin { void setHoverDelay( const Time& hoverDelay ); + const LSPClientServerManager& getClientManager() const; + protected: const PluginManager* mManager{ nullptr }; std::shared_ptr mThreadPool; @@ -92,7 +94,9 @@ class LSPClientPlugin : public UICodeEditorPlugin { size_t lspFilePatternPosition( const std::vector& lsps, const std::vector& patterns ); - void processNotification( const PluginManager::Notification& notification ); + PluginRequestHandle processMessage( const PluginMessage& msg ); + + PluginRequestHandle processCodeCompletionRequest( const PluginMessage& msg ); }; } // namespace ecode diff --git a/src/tools/ecode/plugins/lsp/lspclientserver.cpp b/src/tools/ecode/plugins/lsp/lspclientserver.cpp index b0c5d1ef6..bea79e113 100644 --- a/src/tools/ecode/plugins/lsp/lspclientserver.cpp +++ b/src/tools/ecode/plugins/lsp/lspclientserver.cpp @@ -288,6 +288,7 @@ static void fromJson( LSPServerCapabilities& caps, const json& json ) { fromJson( caps.workspaceFolders, workspace["workspaceFolders"] ); } caps.selectionRangeProvider = toBoolOrObject( json, "selectionRangeProvider" ); + caps.ready = true; } static std::vector parseDiagnosticsArr( const json& result ) { @@ -657,6 +658,54 @@ static std::vector parseDocumentCompletion( const json& resul return ret; } +static LSPSignatureInformation parseSignatureInformation( const json& json ) { + LSPSignatureInformation info; + + info.label = json.value( MEMBER_LABEL, "" ); + info.documentation = parseMarkupContent( json.value( MEMBER_DOCUMENTATION, {} ) ); + const auto& params = json.at( "parameters" ); + for ( const auto& par : params ) { + auto label = par.at( MEMBER_LABEL ); + int begin = -1, end = -1; + if ( label.is_array() ) { + auto& range = label; + if ( range.size() == 2 ) { + begin = range[0].get(); + end = range[1].get(); + if ( begin > (int)info.label.length() ) + begin = -1; + if ( end > (int)info.label.length() ) + end = -1; + } + } else { + auto sub = label.get(); + if ( sub.length() ) { + begin = info.label.find_first_of( sub ); + if ( begin >= 0 ) + end = begin + sub.length(); + } + } + info.parameters.push_back( { begin, end } ); + } + return info; +} + +static LSPSignatureHelp parseSignatureHelp( const json& sig ) { + LSPSignatureHelp ret; + const auto& sigInfos = sig.at( "signatures" ); + for ( const auto& info : sigInfos ) + ret.signatures.push_back( parseSignatureInformation( info ) ); + ret.activeSignature = sig.value( "activeSignature", 0 ); + ret.activeParameter = sig.value( "activeParameter", 0 ); + ret.activeSignature = eemin( eemax( ret.activeSignature, 0 ), (int)ret.signatures.size() ); + ret.activeParameter = eemax( ret.activeParameter, 0 ); + if ( !ret.signatures.empty() ) { + ret.activeParameter = eemin( + ret.activeParameter, (int)ret.signatures.at( ret.activeSignature ).parameters.size() ); + } + return ret; +} + static std::shared_ptr parseSelectionRange( const json& selectionRange ) { auto current = std::make_shared( LSPSelectionRange{} ); std::shared_ptr ret = current; @@ -729,7 +778,7 @@ void LSPClientServer::initialize() { write( newRequest( "initialize", params ), - [&]( const json& resp ) { + [&]( const IdType&, const json& resp ) { #ifndef EE_DEBUG try { #endif @@ -746,7 +795,7 @@ void LSPClientServer::initialize() { write( newRequest( "initialized" ) ); sendQueuedMessages(); }, - [&]( const json& ) {} ); + [&]( const IdType&, const json& ) {} ); } LSPClientServer::LSPClientServer( LSPClientServerManager* manager, const String::HashType& id, @@ -795,18 +844,20 @@ const LSPServerCapabilities& LSPClientServer::getCapabilities() const { return mCapabilities; } -LSPClientServer::RequestHandle LSPClientServer::cancel( int reqid ) { +LSPClientServer::LSPRequestHandle LSPClientServer::cancel( int reqid ) { Lock l( mHandlersMutex ); if ( mHandlers.erase( reqid ) > 0 ) { auto params = json{ MEMBER_ID, reqid }; return write( newRequest( "$/cancelRequest", params ) ); } - return RequestHandle(); + return LSPRequestHandle(); } -LSPClientServer::RequestHandle LSPClientServer::write( const json& msg, const JsonReplyHandler& h, - const JsonReplyHandler& eh, const int id ) { - RequestHandle ret; +LSPClientServer::LSPRequestHandle LSPClientServer::write( const json& msg, + const JsonReplyHandler& h, + const JsonReplyHandler& eh, + const int id ) { + LSPRequestHandle ret; ret.server = this; if ( !mProcess.isAlive() ) @@ -818,7 +869,7 @@ LSPClientServer::RequestHandle LSPClientServer::write( const json& msg, const Js // notification == no handler if ( h ) { ob[MEMBER_ID] = ++mLastMsgId; - ret.id = mLastMsgId; + ret.mId = mLastMsgId; Lock l( mHandlersMutex ); mHandlers[mLastMsgId] = { h, eh }; } else if ( id ) { @@ -848,24 +899,24 @@ LSPClientServer::RequestHandle LSPClientServer::write( const json& msg, const Js return ret; } -LSPClientServer::RequestHandle LSPClientServer::send( const json& msg, const JsonReplyHandler& h, - const JsonReplyHandler& eh ) { +LSPClientServer::LSPRequestHandle LSPClientServer::send( const json& msg, const JsonReplyHandler& h, + const JsonReplyHandler& eh ) { if ( mProcess.isAlive() ) { return write( msg, h, eh ); } else { Log::debug( "LSPClientServer server %s Send for non-running server: %s", mLSP.name.c_str(), mLSP.name.c_str() ); } - return RequestHandle(); + return LSPRequestHandle(); } -LSPClientServer::RequestHandle LSPClientServer::didOpen( const URI& document, - const std::string& text, int version ) { +LSPClientServer::LSPRequestHandle LSPClientServer::didOpen( const URI& document, + const std::string& text, int version ) { auto params = textDocumentParams( textDocumentItem( document, mLSP.language, text, version ) ); return send( newRequest( "textDocument/didOpen", params ) ); } -LSPClientServer::RequestHandle LSPClientServer::didOpen( TextDocument* doc, int version ) { +LSPClientServer::LSPRequestHandle LSPClientServer::didOpen( TextDocument* doc, int version ) { if ( doc->isDirty() ) { IOStreamString text; doc->save( text, true ); @@ -877,21 +928,21 @@ LSPClientServer::RequestHandle LSPClientServer::didOpen( TextDocument* doc, int } } -LSPClientServer::RequestHandle LSPClientServer::didSave( const URI& document, - const std::string& text ) { +LSPClientServer::LSPRequestHandle 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 ) { +LSPClientServer::LSPRequestHandle LSPClientServer::didSave( TextDocument* doc ) { return didSave( doc->getURI(), mCapabilities.textDocumentSync.save.includeText ? doc->getText().toUtf8() : "" ); } -LSPClientServer::RequestHandle +LSPClientServer::LSPRequestHandle LSPClientServer::didChange( const URI& document, int version, const std::string& text, const std::vector& change ) { auto params = textDocumentParams( document, version ); @@ -899,14 +950,14 @@ LSPClientServer::didChange( const URI& document, int version, const std::string& return send( newRequest( "textDocument/didChange", params ) ); } -LSPClientServer::RequestHandle +LSPClientServer::LSPRequestHandle LSPClientServer::didChange( TextDocument* doc, const std::vector& change ) { Lock l( mClientsMutex ); auto it = mClients.find( doc ); if ( it != mClients.end() ) return didChange( doc->getURI(), it->second->getVersion(), change.empty() ? doc->getText().toUtf8() : "", change ); - return RequestHandle(); + return LSPRequestHandle(); } void LSPClientServer::updateDirty() { @@ -928,16 +979,24 @@ bool LSPClientServer::hasDocument( TextDocument* doc ) const { return std::find( mDocs.begin(), mDocs.end(), doc ) != mDocs.end(); } +bool LSPClientServer::hasDocument( const URI& uri ) const { + for ( const auto& doc : mDocs ) { + if ( doc->getURI() == uri ) + return true; + } + return false; +} + bool LSPClientServer::hasDocuments() const { return !mDocs.empty(); } -LSPClientServer::RequestHandle LSPClientServer::didClose( const URI& document ) { +LSPClientServer::LSPRequestHandle LSPClientServer::didClose( const URI& document ) { auto params = textDocumentParams( document ); return send( newRequest( "textDocument/didClose", params ) ); } -LSPClientServer::RequestHandle LSPClientServer::didClose( TextDocument* doc ) { +LSPClientServer::LSPRequestHandle LSPClientServer::didClose( TextDocument* doc ) { auto ret = didClose( doc->getURI() ); Lock l( mClientsMutex ); if ( mClients.erase( doc ) > 0 ) { @@ -956,41 +1015,41 @@ const std::shared_ptr& LSPClientServer::getThreadPool() const { return mManager->getThreadPool(); } -LSPClientServer::RequestHandle LSPClientServer::documentSymbols( const URI& document, - const JsonReplyHandler& h, - const JsonReplyHandler& eh ) { +LSPClientServer::LSPRequestHandle LSPClientServer::documentSymbols( const URI& document, + const JsonReplyHandler& h, + const JsonReplyHandler& eh ) { auto params = textDocumentParams( document ); return send( newRequest( "textDocument/documentSymbol", params ), h, eh ); } -LSPClientServer::RequestHandle +LSPClientServer::LSPRequestHandle LSPClientServer::documentSymbols( const URI& document, const ReplyHandler>& h, const ReplyHandler& eh ) { return documentSymbols( document, - [h]( const json& json ) { + [h]( const IdType& id, const json& json ) { if ( h ) - h( parseDocumentSymbols( json ) ); + h( id, parseDocumentSymbols( json ) ); }, - [eh]( const json& json ) { + [eh]( const IdType& id, const json& json ) { if ( eh ) - eh( parseResponseError( json ) ); + eh( id, parseResponseError( json ) ); } ); } -LSPClientServer::RequestHandle LSPClientServer::workspaceSymbol( const std::string& querySymbol, - const JsonReplyHandler& h ) { +LSPClientServer::LSPRequestHandle LSPClientServer::workspaceSymbol( const std::string& querySymbol, + const JsonReplyHandler& h ) { auto params = json{ { MEMBER_QUERY, querySymbol } }; return send( newRequest( "workspace/symbol", params ), h ); } -LSPClientServer::RequestHandle +LSPClientServer::LSPRequestHandle LSPClientServer::workspaceSymbol( const std::string& querySymbol, const SymbolInformationHandler& h ) { - return workspaceSymbol( querySymbol, [h]( const json& json ) { + return workspaceSymbol( querySymbol, [h]( const IdType& id, const json& json ) { if ( h ) - h( parseWorkspaceSymbols( json ) ); + h( id, parseWorkspaceSymbols( json ) ); } ); } @@ -1031,9 +1090,9 @@ static json newError( const LSPErrorCode& code, const std::string& msg ) { void LSPClientServer::publishDiagnostics( const json& msg ) { LSPPublishDiagnosticsParams res = parsePublishDiagnostics( msg[MEMBER_PARAMS] ); if ( mManager && mManager->getPluginManager() && mManager->getPlugin() ) { - mManager->getPluginManager()->pushNotification( mManager->getPlugin(), - PluginManager::PublishDiagnostics, - PluginManager::Diagnostics, &res ); + mManager->getPluginManager()->sendBroadcast( mManager->getPlugin(), + PluginMessageType::Diagnostics, + PluginMessageFormat::Diagnostics, &res ); } Log::debug( "LSPClientServer::publishDiagnostics: %s - returned %zu items", res.uri.toString().c_str(), res.diagnostics.size() ); @@ -1158,9 +1217,9 @@ void LSPClientServer::readStdOut( const char* bytes, size_t n ) { auto& h = handler.second.first; auto& eh = handler.second.second; if ( res.contains( MEMBER_ERROR ) && eh ) { - eh( res[MEMBER_ERROR] ); + eh( msgid, res[MEMBER_ERROR] ); } else { - h( res[MEMBER_RESULT] ); + h( msgid, res[MEMBER_RESULT] ); } } else { Log::debug( "LSPClientServer::readStdOut server %s unexpected reply id: %d", @@ -1202,36 +1261,37 @@ void LSPClientServer::goToLocation( const json& res ) { mManager->goToLocation( locs.front() ); } -LSPClientServer::RequestHandle LSPClientServer::getAndGoToLocation( const URI& document, - const TextPosition& pos, - const std::string& search ) { +LSPClientServer::LSPRequestHandle LSPClientServer::getAndGoToLocation( const URI& document, + const TextPosition& pos, + const std::string& search ) { auto params = textDocumentPositionParams( document, pos ); - return send( newRequest( search, params ), [this]( json res ) { goToLocation( res ); } ); + return send( newRequest( search, params ), + [this]( const IdType&, const json& res ) { goToLocation( res ); } ); } -LSPClientServer::RequestHandle LSPClientServer::documentDefinition( const URI& document, - const TextPosition& pos ) { +LSPClientServer::LSPRequestHandle LSPClientServer::documentDefinition( const URI& document, + const TextPosition& pos ) { return getAndGoToLocation( document, pos, "textDocument/definition" ); } -LSPClientServer::RequestHandle LSPClientServer::documentDeclaration( const URI& document, - const TextPosition& pos ) { +LSPClientServer::LSPRequestHandle LSPClientServer::documentDeclaration( const URI& document, + const TextPosition& pos ) { return getAndGoToLocation( document, pos, "textDocument/declaration" ); } -LSPClientServer::RequestHandle LSPClientServer::documentTypeDefinition( const URI& document, - const TextPosition& pos ) { +LSPClientServer::LSPRequestHandle +LSPClientServer::documentTypeDefinition( const URI& document, const TextPosition& pos ) { return getAndGoToLocation( document, pos, "textDocument/typeDefinition" ); } -LSPClientServer::RequestHandle LSPClientServer::documentImplementation( const URI& document, - const TextPosition& pos ) { +LSPClientServer::LSPRequestHandle +LSPClientServer::documentImplementation( const URI& document, const TextPosition& pos ) { return getAndGoToLocation( document, pos, "textDocument/implementation" ); } -LSPClientServer::RequestHandle LSPClientServer::switchSourceHeader( const URI& document ) { +LSPClientServer::LSPRequestHandle LSPClientServer::switchSourceHeader( const URI& document ) { return send( newRequest( "textDocument/switchSourceHeader", textDocumentURI( document ) ), - [this]( json res ) { + [this]( const IdType&, json res ) { if ( res.is_string() ) { mManager->goToLocation( { res.get(), TextRange{ { 0, 0 }, { 0, 0 } } } ); @@ -1239,78 +1299,95 @@ LSPClientServer::RequestHandle LSPClientServer::switchSourceHeader( const URI& d } ); } -LSPClientServer::RequestHandle +LSPClientServer::LSPRequestHandle LSPClientServer::didChangeWorkspaceFolders( const std::vector& added, const std::vector& removed ) { auto params = changeWorkspaceFoldersParams( added, removed ); return send( newRequest( "workspace/didChangeWorkspaceFolders", params ) ); } -LSPClientServer::RequestHandle LSPClientServer::documentCodeAction( +LSPClientServer::LSPRequestHandle LSPClientServer::documentCodeAction( const URI& document, const TextRange& range, const std::vector& kinds, std::vector diagnostics, const JsonReplyHandler& h ) { auto params = codeActionParams( document, range, kinds, std::move( diagnostics ) ); return send( newRequest( "textDocument/codeAction", params ), h ); } -LSPClientServer::RequestHandle LSPClientServer::documentCodeAction( +LSPClientServer::LSPRequestHandle LSPClientServer::documentCodeAction( const URI& document, const TextRange& range, const std::vector& kinds, std::vector diagnostics, const CodeActionHandler& h ) { - return documentCodeAction( document, range, kinds, diagnostics, [h]( const json& json ) { - if ( h ) - h( parseCodeAction( json ) ); - } ); + return documentCodeAction( document, range, kinds, diagnostics, + [h]( const IdType& id, const json& json ) { + if ( h ) + h( id, parseCodeAction( json ) ); + } ); } -LSPClientServer::RequestHandle LSPClientServer::documentHover( const URI& document, - const TextPosition& pos, - const JsonReplyHandler& h ) { +LSPClientServer::LSPRequestHandle LSPClientServer::documentHover( const URI& document, + const TextPosition& pos, + const JsonReplyHandler& h ) { auto params = textDocumentPositionParams( document, pos ); return send( newRequest( "textDocument/hover", params ), h ); } -LSPClientServer::RequestHandle LSPClientServer::documentHover( const URI& document, - const TextPosition& pos, - const HoverHandler& h ) { - return documentHover( document, pos, [h]( const json& json ) { +LSPClientServer::LSPRequestHandle LSPClientServer::documentHover( const URI& document, + const TextPosition& pos, + const HoverHandler& h ) { + return documentHover( document, pos, [h]( const IdType& id, const json& json ) { if ( h ) - h( parseHover( json ) ); + h( id, parseHover( json ) ); } ); } -LSPClientServer::RequestHandle LSPClientServer::documentCompletion( const URI& document, - const TextPosition& pos, - const JsonReplyHandler& h ) { +LSPClientServer::LSPRequestHandle LSPClientServer::documentCompletion( const URI& document, + const TextPosition& pos, + const JsonReplyHandler& h ) { auto params = textDocumentPositionParams( document, pos ); return send( newRequest( "textDocument/completion", params ), h ); } -LSPClientServer::RequestHandle LSPClientServer::documentCompletion( const URI& document, - const TextPosition& pos, - const CompletionHandler& h ) { - return documentCompletion( document, pos, [h]( const json& json ) { +LSPClientServer::LSPRequestHandle +LSPClientServer::documentCompletion( const URI& document, const TextPosition& pos, + const CompletionHandler& h ) { + return documentCompletion( document, pos, [h]( const IdType& id, const json& json ) { if ( h ) - h( parseDocumentCompletion( json ) ); + h( id, parseDocumentCompletion( json ) ); } ); } -LSPClientServer::RequestHandle +LSPClientServer::LSPRequestHandle LSPClientServer::signatureHelp( const URI& document, + const TextPosition& pos, + const JsonReplyHandler& h ) { + auto params = textDocumentPositionParams( document, pos ); + return send( newRequest( "textDocument/signatureHelp", params ), h ); +} + +LSPClientServer::LSPRequestHandle LSPClientServer::signatureHelp( const URI& document, + const TextPosition& pos, + const SignatureHelpHandler& h ) { + return signatureHelp( document, pos, [h]( const IdType& id, const json& json ) { + if ( h ) + h( id, parseSignatureHelp( json ) ); + } ); +} + +LSPClientServer::LSPRequestHandle LSPClientServer::selectionRange( const URI& document, const std::vector& positions, const JsonReplyHandler& h ) { auto params = textDocumentPositionsParams( document, positions ); return send( newRequest( "textDocument/selectionRange", params ), h ); } -LSPClientServer::RequestHandle +LSPClientServer::LSPRequestHandle LSPClientServer::selectionRange( const URI& document, const std::vector& positions, const SelectionRangeHandler& h ) { - return selectionRange( document, positions, [h]( const json& json ) { + return selectionRange( document, positions, [h]( const IdType& id, const json& json ) { if ( h ) - h( parseSelectionRanges( json ) ); + h( id, parseSelectionRanges( json ) ); } ); } -LSPClientServer::RequestHandle +LSPClientServer::LSPRequestHandle LSPClientServer::documentSemanticTokensFull( const URI& document, bool delta, const std::string& requestId, const TextRange& range, const JsonReplyHandler& h ) { diff --git a/src/tools/ecode/plugins/lsp/lspclientserver.hpp b/src/tools/ecode/plugins/lsp/lspclientserver.hpp index 069afce29..6324cdb3b 100644 --- a/src/tools/ecode/plugins/lsp/lspclientserver.hpp +++ b/src/tools/ecode/plugins/lsp/lspclientserver.hpp @@ -1,9 +1,10 @@ #ifndef ECODE_LSPCLIENTSERVER_HPP #define ECODE_LSPCLIENTSERVER_HPP -#include "lspprotocol.hpp" +#include "../pluginmanager.hpp" #include "lspdefinition.hpp" #include "lspdocumentclient.hpp" +#include "lspprotocol.hpp" #include #include #include @@ -25,7 +26,8 @@ class LSPClientServerManager; class LSPClientServer { public: - template using ReplyHandler = std::function; + using IdType = PluginIDType; + template using ReplyHandler = std::function; using JsonReplyHandler = ReplyHandler; using CodeActionHandler = ReplyHandler>; @@ -33,19 +35,18 @@ class LSPClientServer { using CompletionHandler = ReplyHandler>; using SymbolInformationHandler = ReplyHandler>; using SelectionRangeHandler = ReplyHandler>>; + using SignatureHelpHandler = ReplyHandler; + + class LSPRequestHandle : public PluginRequestHandle { + public: + void cancel() { + if ( server && mId != 0 ) + server->cancel( mId ); + } - class RequestHandle { private: friend class LSPClientServer; LSPClientServer* server; - int id = 0; - - public: - RequestHandle& cancel() { - if ( server ) - server->cancel( id ); - return *this; - } }; LSPClientServer( LSPClientServerManager* manager, const String::HashType& id, @@ -63,100 +64,111 @@ class LSPClientServer { const std::shared_ptr& getThreadPool() const; - RequestHandle cancel( int id ); + LSPRequestHandle cancel( int id ); - RequestHandle send( const json& msg, const JsonReplyHandler& h = nullptr, - const JsonReplyHandler& eh = nullptr ); + LSPRequestHandle send( const json& msg, const JsonReplyHandler& h = nullptr, + const JsonReplyHandler& eh = nullptr ); const LSPDefinition& getDefinition() const { return mLSP; } - RequestHandle documentSymbols( const URI& document, const JsonReplyHandler& h, - const JsonReplyHandler& eh ); + LSPRequestHandle documentSymbols( const URI& document, const JsonReplyHandler& h, + const JsonReplyHandler& eh ); - RequestHandle documentSymbols( const URI& document, - const ReplyHandler>& h, - const ReplyHandler& eh ); + LSPRequestHandle documentSymbols( const URI& document, + const ReplyHandler>& h, + const ReplyHandler& eh ); - RequestHandle didOpen( const URI& document, const std::string& text, int version ); + LSPRequestHandle didOpen( const URI& document, const std::string& text, int version ); - RequestHandle didOpen( TextDocument* doc, int version ); + LSPRequestHandle didOpen( TextDocument* doc, int version ); - RequestHandle didSave( const URI& document, const std::string& text ); + LSPRequestHandle didSave( const URI& document, const std::string& text ); - RequestHandle didSave( TextDocument* doc ); + LSPRequestHandle didSave( TextDocument* doc ); - RequestHandle didClose( const URI& document ); + LSPRequestHandle didClose( const URI& document ); - RequestHandle didClose( TextDocument* document ); + LSPRequestHandle didClose( TextDocument* document ); - RequestHandle didChange( const URI& document, int version, const std::string& text, - const std::vector& change = {} ); + LSPRequestHandle didChange( const URI& document, int version, const std::string& text, + const std::vector& change = {} ); - RequestHandle didChange( TextDocument* doc, - const std::vector& change = {} ); + LSPRequestHandle didChange( TextDocument* doc, + const std::vector& change = {} ); - RequestHandle documentDefinition( const URI& document, const TextPosition& pos ); + LSPRequestHandle documentDefinition( const URI& document, const TextPosition& pos ); - RequestHandle documentDeclaration( const URI& document, const TextPosition& pos ); + LSPRequestHandle documentDeclaration( const URI& document, const TextPosition& pos ); - RequestHandle documentTypeDefinition( const URI& document, const TextPosition& pos ); + LSPRequestHandle documentTypeDefinition( const URI& document, const TextPosition& pos ); - RequestHandle documentImplementation( const URI& document, const TextPosition& pos ); + LSPRequestHandle documentImplementation( const URI& document, const TextPosition& pos ); void updateDirty(); bool hasDocument( TextDocument* doc ) const; + bool hasDocument( const URI& uri ) const; + bool hasDocuments() const; - RequestHandle didChangeWorkspaceFolders( const std::vector& added, - const std::vector& removed ); + LSPRequestHandle didChangeWorkspaceFolders( const std::vector& added, + const std::vector& removed ); void publishDiagnostics( const json& msg ); void workDoneProgress( const LSPWorkDoneProgressParams& workDoneParams ); - RequestHandle getAndGoToLocation( const URI& document, const TextPosition& pos, - const std::string& search ); + LSPRequestHandle getAndGoToLocation( const URI& document, const TextPosition& pos, + const std::string& search ); - RequestHandle switchSourceHeader( const URI& document ); + LSPRequestHandle switchSourceHeader( const URI& document ); - RequestHandle documentCodeAction( const URI& document, const TextRange& range, - const std::vector& kinds, - std::vector diagnostics, - const JsonReplyHandler& h ); + LSPRequestHandle documentCodeAction( const URI& document, const TextRange& range, + const std::vector& kinds, + std::vector diagnostics, + const JsonReplyHandler& h ); - RequestHandle documentCodeAction( const URI& document, const TextRange& range, - const std::vector& kinds, - std::vector diagnostics, - const CodeActionHandler& h ); + LSPRequestHandle documentCodeAction( const URI& document, const TextRange& range, + const std::vector& kinds, + std::vector diagnostics, + const CodeActionHandler& h ); - RequestHandle documentHover( const URI& document, const TextPosition& pos, - const JsonReplyHandler& h ); + LSPRequestHandle documentHover( const URI& document, const TextPosition& pos, + const JsonReplyHandler& h ); - RequestHandle documentHover( const URI& document, const TextPosition& pos, - const HoverHandler& h ); + LSPRequestHandle documentHover( const URI& document, const TextPosition& pos, + const HoverHandler& h ); - RequestHandle documentCompletion( const URI& document, const TextPosition& pos, - const JsonReplyHandler& h ); + LSPRequestHandle documentCompletion( const URI& document, const TextPosition& pos, + const JsonReplyHandler& h ); - RequestHandle documentCompletion( const URI& document, const TextPosition& pos, - const CompletionHandler& h ); + LSPRequestHandle documentCompletion( const URI& document, const TextPosition& pos, + const CompletionHandler& h ); - RequestHandle workspaceSymbol( const std::string& querySymbol, const JsonReplyHandler& h ); + LSPRequestHandle workspaceSymbol( const std::string& querySymbol, const JsonReplyHandler& h ); - RequestHandle workspaceSymbol( const std::string& querySymbol, - const SymbolInformationHandler& h ); + LSPRequestHandle workspaceSymbol( const std::string& querySymbol, + const SymbolInformationHandler& h ); - RequestHandle selectionRange( const URI& document, const std::vector& positions, - const JsonReplyHandler& h ); + LSPRequestHandle selectionRange( const URI& document, + const std::vector& positions, + const JsonReplyHandler& h ); - RequestHandle selectionRange( const URI& document, const std::vector& positions, - const SelectionRangeHandler& h ); + LSPRequestHandle selectionRange( const URI& document, + const std::vector& positions, + const SelectionRangeHandler& h ); - RequestHandle documentSemanticTokensFull( const URI& document, bool delta, - const std::string& requestId, const TextRange& range, - const JsonReplyHandler& h ); + LSPRequestHandle documentSemanticTokensFull( const URI& document, bool delta, + const std::string& requestId, + const TextRange& range, + const JsonReplyHandler& h ); + + LSPRequestHandle signatureHelp( const URI& document, const TextPosition& pos, + const JsonReplyHandler& h ); + + LSPRequestHandle signatureHelp( const URI& document, const TextPosition& pos, + const SignatureHelpHandler& h ); protected: LSPClientServerManager* mManager{ nullptr }; @@ -186,8 +198,8 @@ class LSPClientServer { void readStdErr( const char* bytes, size_t n ); - RequestHandle write( const json& msg, const JsonReplyHandler& h = nullptr, - const JsonReplyHandler& eh = nullptr, const int id = 0 ); + LSPRequestHandle write( const json& msg, const JsonReplyHandler& h = nullptr, + const JsonReplyHandler& eh = nullptr, const int id = 0 ); void initialize(); diff --git a/src/tools/ecode/plugins/lsp/lspclientservermanager.cpp b/src/tools/ecode/plugins/lsp/lspclientservermanager.cpp index 849754748..999d3ba61 100644 --- a/src/tools/ecode/plugins/lsp/lspclientservermanager.cpp +++ b/src/tools/ecode/plugins/lsp/lspclientservermanager.cpp @@ -203,6 +203,16 @@ LSPClientServerManager::getLSPClientServers( const std::shared_ptr return servers; } +std::vector LSPClientServerManager::getLSPClientServers( const URI& uri ) { + std::vector servers; + Lock l( mClientsMutex ); + for ( auto& server : mClients ) { + if ( server.second->hasDocument( uri ) ) + servers.push_back( server.second.get() ); + } + return servers; +} + LSPClientServer* LSPClientServerManager::getOneLSPClientServer( UICodeEditor* editor ) { return getOneLSPClientServer( editor->getDocumentRef() ); } @@ -217,4 +227,13 @@ LSPClientServerManager::getOneLSPClientServer( const std::shared_ptrhasDocument( uri ) ) + return server.second.get(); + } + return nullptr; +} + } // namespace ecode diff --git a/src/tools/ecode/plugins/lsp/lspclientservermanager.hpp b/src/tools/ecode/plugins/lsp/lspclientservermanager.hpp index d998be635..afbabde59 100644 --- a/src/tools/ecode/plugins/lsp/lspclientservermanager.hpp +++ b/src/tools/ecode/plugins/lsp/lspclientservermanager.hpp @@ -41,10 +41,14 @@ class LSPClientServerManager { std::vector getLSPClientServers( const std::shared_ptr& doc ); + std::vector getLSPClientServers( const URI& uri ); + LSPClientServer* getOneLSPClientServer( UICodeEditor* editor ); LSPClientServer* getOneLSPClientServer( const std::shared_ptr& doc ); + LSPClientServer* getOneLSPClientServer( const URI& uri ); + void getAndGoToLocation( const std::shared_ptr& doc, const std::string& search ); const PluginManager* getPluginManager() const; diff --git a/src/tools/ecode/plugins/lsp/lspprotocol.hpp b/src/tools/ecode/plugins/lsp/lspprotocol.hpp index c95dd299d..e863de6d4 100644 --- a/src/tools/ecode/plugins/lsp/lspprotocol.hpp +++ b/src/tools/ecode/plugins/lsp/lspprotocol.hpp @@ -80,6 +80,7 @@ struct LSPWorkspaceFoldersServerCapabilities { }; struct LSPServerCapabilities { + bool ready = false; LSPTextDocumentSyncOptions textDocumentSync; bool hoverProvider = false; LSPCompletionOptions completionProvider; @@ -302,6 +303,33 @@ struct LSPSelectionRange { std::shared_ptr parent; }; +struct LSPParameterInformation { + // offsets into overall signature label + // (-1 if invalid) + int start; + int end; +}; + +struct LSPSignatureInformation { + std::string label; + LSPMarkupContent documentation; + std::vector parameters; +}; + +struct LSPSignatureHelp { + std::vector signatures; + int activeSignature; + int activeParameter; +}; + +struct LSPConverter { + static TextPosition fromJSON( const nlohmann::json& data ) { + if ( data.contains( "line" ) && data.contains( "character" ) ) + return { data["line"].get(), data["character"].get() }; + return {}; + } +}; + } // namespace ecode #endif // ECODE_LSPCLIENTPROTOCOL_HPP diff --git a/src/tools/ecode/plugins/pluginmanager.cpp b/src/tools/ecode/plugins/pluginmanager.cpp index 5e5e8ee25..b58f4e788 100644 --- a/src/tools/ecode/plugins/pluginmanager.cpp +++ b/src/tools/ecode/plugins/pluginmanager.cpp @@ -13,6 +13,7 @@ PluginManager::PluginManager( const std::string& resourcesPath, const std::strin mResourcesPath( resourcesPath ), mPluginsPath( pluginsPath ), mThreadPool( pool ) {} PluginManager::~PluginManager() { + mClosing = true; for ( auto& plugin : mPlugins ) eeDelete( plugin.second ); } @@ -105,26 +106,54 @@ const std::string& PluginManager::getWorkspaceFolder() const { void PluginManager::setWorkspaceFolder( const std::string& workspaceFolder ) { mWorkspaceFolder = workspaceFolder; json data{ { "folder", mWorkspaceFolder } }; - sendNotification( NotificationType::WorkspaceFolderChanged, NotificationFormat::JSON, &data ); + sendBroadcast( PluginMessageType::WorkspaceFolderChanged, PluginMessageFormat::JSON, &data ); } -void PluginManager::pushNotification( UICodeEditorPlugin* pluginWho, NotificationType notification, - NotificationFormat format, void* data ) const { +PluginRequestHandle PluginManager::sendRequest( UICodeEditorPlugin* pluginWho, + PluginMessageType type, PluginMessageFormat format, + const void* data ) const { + if ( mClosing ) + return PluginRequestHandle::empty(); + for ( const auto& plugin : mSubscribedPlugins ) { + if ( pluginWho->getId() != plugin.first ) { + auto handle = plugin.second( { type, format, data } ); + if ( !handle.isEmpty() ) + return handle; + } + } + return PluginRequestHandle::empty(); +} + +void PluginManager::sendResponse( UICodeEditorPlugin* pluginWho, PluginMessageType type, + PluginMessageFormat format, const void* data, + const PluginIDType& responseID ) const { + if ( mClosing ) + return; for ( const auto& plugin : mSubscribedPlugins ) if ( pluginWho->getId() != plugin.first ) - plugin.second( { notification, format, data } ); + plugin.second( { type, format, data, responseID } ); } -void PluginManager::subscribeNotifications( UICodeEditorPlugin* plugin, - std::function cb ) const { +void PluginManager::sendBroadcast( UICodeEditorPlugin* pluginWho, PluginMessageType type, + PluginMessageFormat format, void* data ) const { + if ( mClosing ) + return; + for ( const auto& plugin : mSubscribedPlugins ) + if ( pluginWho->getId() != plugin.first ) + plugin.second( { type, format, data, -1 } ); +} + +void PluginManager::subscribeMessages( + UICodeEditorPlugin* plugin, + std::function cb ) const { const_cast( this )->mSubscribedPlugins[plugin->getId()] = cb; if ( !mWorkspaceFolder.empty() ) { json data{ { "folder", mWorkspaceFolder } }; - cb( { NotificationType::WorkspaceFolderChanged, NotificationFormat::JSON, &data } ); + cb( { PluginMessageType::WorkspaceFolderChanged, PluginMessageFormat::JSON, &data } ); } } -void PluginManager::unsubscribeNotifications( UICodeEditorPlugin* plugin ) const { +void PluginManager::unsubscribeMessages( UICodeEditorPlugin* plugin ) const { const_cast( this )->mSubscribedPlugins.erase( plugin->getId() ); } @@ -132,8 +161,10 @@ void PluginManager::setSplitter( UICodeEditorSplitter* splitter ) { mSplitter = splitter; } -void PluginManager::sendNotification( const NotificationType& notification, - const NotificationFormat& format, void* data ) { +void PluginManager::sendBroadcast( const PluginMessageType& notification, + const PluginMessageFormat& format, void* data ) { + if ( mClosing ) + return; for ( const auto& plugin : mSubscribedPlugins ) plugin.second( { notification, format, data } ); } diff --git a/src/tools/ecode/plugins/pluginmanager.hpp b/src/tools/ecode/plugins/pluginmanager.hpp index c1349385b..f52270625 100644 --- a/src/tools/ecode/plugins/pluginmanager.hpp +++ b/src/tools/ecode/plugins/pluginmanager.hpp @@ -56,22 +56,71 @@ struct PluginDefinition { PluginVersion version; }; +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 + Undefined +}; + +enum class PluginMessageFormat { JSON, Diagnostics, CodeCompletion, LSPClientPlugin }; + +using PluginIDType = Int64; + +class LSPClientPlugin; + +struct PluginMessage { + PluginMessageType type; + PluginMessageFormat format; + const void* data; + PluginIDType responseID{ 0 }; // 0 if it's not a response; + + const nlohmann::json& asJSON() const { return *static_cast( data ); } + + bool isJSON() const { return format == PluginMessageFormat::JSON; } + + const LSPPublishDiagnosticsParams& asDiagnostics() const { + return *static_cast( data ); + } + + const std::vector& asCodeCompletion() const { + return *static_cast*>( data ); + } + + const LSPClientPlugin* asLSPClientPlugin() const { + return static_cast( data ); + } + + bool isResponse() const { return -1 != responseID && 0 != responseID; } + + bool isRequest() const { return -1 != responseID && 0 == responseID; } + + bool isBroadcast() const { return -1 == responseID; } +}; + +class PluginRequestHandle { + public: + static PluginRequestHandle broadcast() { return PluginRequestHandle( -1 ); } + + static PluginRequestHandle empty() { return PluginRequestHandle(); } + + PluginRequestHandle() {} + PluginRequestHandle( int id ) : mId( id ) {} + virtual PluginIDType id() const { return mId; } + virtual void cancel() {} + + bool isEmpty() const { return mId == 0; } + + bool isBroadcast() const { return mId == -1; } + + protected: + PluginIDType mId{ 0 }; +}; + class PluginManager { public: - enum NotificationType { WorkspaceFolderChanged, PublishDiagnostics }; - enum NotificationFormat { JSON, Diagnostics }; - struct Notification { - NotificationType type; - NotificationFormat format; - void* data; - - nlohmann::json& asJSON() const { return *static_cast( data ); } - - LSPPublishDiagnosticsParams& asDiagnostics() const { - return *static_cast( data ); - } - }; - static constexpr int versionNumber( int major, int minor, int patch ) { return ( (major)*1000 + (minor)*100 + ( patch ) ); } @@ -117,13 +166,23 @@ class PluginManager { void setWorkspaceFolder( const std::string& workspaceFolder ); - void pushNotification( UICodeEditorPlugin* pluginWho, NotificationType, NotificationFormat, - void* data ) const; + PluginRequestHandle sendRequest( UICodeEditorPlugin* pluginWho, PluginMessageType type, + PluginMessageFormat format, const void* data ) const; - void subscribeNotifications( UICodeEditorPlugin* plugin, - std::function cb ) const; + void sendResponse( UICodeEditorPlugin* pluginWho, PluginMessageType type, + PluginMessageFormat format, const void* data, + const PluginIDType& responseID ) const; - void unsubscribeNotifications( UICodeEditorPlugin* plugin ) const; + void sendBroadcast( UICodeEditorPlugin* pluginWho, PluginMessageType, PluginMessageFormat, + void* data ) const; + + void sendBroadcast( const PluginMessageType& notification, const PluginMessageFormat& format, + void* data ); + + void subscribeMessages( UICodeEditorPlugin* plugin, + std::function cb ) const; + + void unsubscribeMessages( UICodeEditorPlugin* plugin ) const; protected: friend class App; @@ -135,14 +194,16 @@ class PluginManager { std::map mDefinitions; std::shared_ptr mThreadPool; UICodeEditorSplitter* mSplitter{ nullptr }; - std::map> mSubscribedPlugins; + std::map> + mSubscribedPlugins; + bool mClosing{ false }; bool hasDefinition( const std::string& id ); void setSplitter( UICodeEditorSplitter* splitter ); - void sendNotification( const NotificationType& notification, const NotificationFormat& format, - void* data ); + PluginRequestHandle sendRequest( const PluginMessageType& notification, + const PluginMessageFormat& format, void* data ); }; class PluginsModel : public Model {