Designing the inter-plugin communication protocol.

This commit is contained in:
Martín Lucas Golini
2022-11-08 02:39:45 -03:00
parent 4306e8018f
commit 85b4b8dbee
11 changed files with 491 additions and 202 deletions

View File

@@ -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<Int64, std::vector<LinterMatch>> 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<std::string> paths;
std::string path( pluginManager->getResourcesPath() + "plugins/linters.json" );
if ( FileSystem::fileExists( path ) )

View File

@@ -140,7 +140,7 @@ class LinterPlugin : public UICodeEditorPlugin {
size_t linterFilePatternPosition( const std::vector<std::string>& patterns );
void processNotification( const PluginManager::Notification& notification );
PluginRequestHandle processMessage( const PluginMessage& notification );
TextDocument* getDocumentFromURI( const URI& uri );

View File

@@ -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<LSPCompletionItem>& 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<std::string> 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<LSPDefinition>& 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

View File

@@ -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<ThreadPool> mThreadPool;
@@ -92,7 +94,9 @@ class LSPClientPlugin : public UICodeEditorPlugin {
size_t lspFilePatternPosition( const std::vector<LSPDefinition>& lsps,
const std::vector<std::string>& patterns );
void processNotification( const PluginManager::Notification& notification );
PluginRequestHandle processMessage( const PluginMessage& msg );
PluginRequestHandle processCodeCompletionRequest( const PluginMessage& msg );
};
} // namespace ecode

View File

@@ -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<LSPDiagnostic> parseDiagnosticsArr( const json& result ) {
@@ -657,6 +658,54 @@ static std::vector<LSPCompletionItem> 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<int>();
end = range[1].get<int>();
if ( begin > (int)info.label.length() )
begin = -1;
if ( end > (int)info.label.length() )
end = -1;
}
} else {
auto sub = label.get<std::string>();
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<LSPSelectionRange> parseSelectionRange( const json& selectionRange ) {
auto current = std::make_shared<LSPSelectionRange>( LSPSelectionRange{} );
std::shared_ptr<LSPSelectionRange> 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<DocumentContentChange>& 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<DocumentContentChange>& 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<ThreadPool>& 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<std::vector<LSPSymbolInformation>>& h,
const ReplyHandler<LSPResponseError>& 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<std::string>(), 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<LSPWorkspaceFolder>& added,
const std::vector<LSPWorkspaceFolder>& 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<std::string>& kinds,
std::vector<LSPDiagnostic> 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<std::string>& kinds,
std::vector<LSPDiagnostic> 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<TextPosition>& 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<TextPosition>& 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 ) {

View File

@@ -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 <eepp/system/process.hpp>
#include <eepp/ui/doc/textdocument.hpp>
#include <eepp/ui/doc/undostack.hpp>
@@ -25,7 +26,8 @@ class LSPClientServerManager;
class LSPClientServer {
public:
template <typename T> using ReplyHandler = std::function<void( const T& )>;
using IdType = PluginIDType;
template <typename T> using ReplyHandler = std::function<void( const IdType& id, const T& )>;
using JsonReplyHandler = ReplyHandler<json>;
using CodeActionHandler = ReplyHandler<std::vector<LSPCodeAction>>;
@@ -33,19 +35,18 @@ class LSPClientServer {
using CompletionHandler = ReplyHandler<std::vector<LSPCompletionItem>>;
using SymbolInformationHandler = ReplyHandler<std::vector<LSPSymbolInformation>>;
using SelectionRangeHandler = ReplyHandler<std::vector<std::shared_ptr<LSPSelectionRange>>>;
using SignatureHelpHandler = ReplyHandler<LSPSignatureHelp>;
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<ThreadPool>& 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<std::vector<LSPSymbolInformation>>& h,
const ReplyHandler<LSPResponseError>& eh );
LSPRequestHandle documentSymbols( const URI& document,
const ReplyHandler<std::vector<LSPSymbolInformation>>& h,
const ReplyHandler<LSPResponseError>& 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<DocumentContentChange>& change = {} );
LSPRequestHandle didChange( const URI& document, int version, const std::string& text,
const std::vector<DocumentContentChange>& change = {} );
RequestHandle didChange( TextDocument* doc,
const std::vector<DocumentContentChange>& change = {} );
LSPRequestHandle didChange( TextDocument* doc,
const std::vector<DocumentContentChange>& 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<LSPWorkspaceFolder>& added,
const std::vector<LSPWorkspaceFolder>& removed );
LSPRequestHandle didChangeWorkspaceFolders( const std::vector<LSPWorkspaceFolder>& added,
const std::vector<LSPWorkspaceFolder>& 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<std::string>& kinds,
std::vector<LSPDiagnostic> diagnostics,
const JsonReplyHandler& h );
LSPRequestHandle documentCodeAction( const URI& document, const TextRange& range,
const std::vector<std::string>& kinds,
std::vector<LSPDiagnostic> diagnostics,
const JsonReplyHandler& h );
RequestHandle documentCodeAction( const URI& document, const TextRange& range,
const std::vector<std::string>& kinds,
std::vector<LSPDiagnostic> diagnostics,
const CodeActionHandler& h );
LSPRequestHandle documentCodeAction( const URI& document, const TextRange& range,
const std::vector<std::string>& kinds,
std::vector<LSPDiagnostic> 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<TextPosition>& positions,
const JsonReplyHandler& h );
LSPRequestHandle selectionRange( const URI& document,
const std::vector<TextPosition>& positions,
const JsonReplyHandler& h );
RequestHandle selectionRange( const URI& document, const std::vector<TextPosition>& positions,
const SelectionRangeHandler& h );
LSPRequestHandle selectionRange( const URI& document,
const std::vector<TextPosition>& 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();

View File

@@ -203,6 +203,16 @@ LSPClientServerManager::getLSPClientServers( const std::shared_ptr<TextDocument>
return servers;
}
std::vector<LSPClientServer*> LSPClientServerManager::getLSPClientServers( const URI& uri ) {
std::vector<LSPClientServer*> 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_ptr<TextDocumen
return nullptr;
}
LSPClientServer* LSPClientServerManager::getOneLSPClientServer( const URI& uri ) {
Lock l( mClientsMutex );
for ( auto& server : mClients ) {
if ( server.second->hasDocument( uri ) )
return server.second.get();
}
return nullptr;
}
} // namespace ecode

View File

@@ -41,10 +41,14 @@ class LSPClientServerManager {
std::vector<LSPClientServer*> getLSPClientServers( const std::shared_ptr<TextDocument>& doc );
std::vector<LSPClientServer*> getLSPClientServers( const URI& uri );
LSPClientServer* getOneLSPClientServer( UICodeEditor* editor );
LSPClientServer* getOneLSPClientServer( const std::shared_ptr<TextDocument>& doc );
LSPClientServer* getOneLSPClientServer( const URI& uri );
void getAndGoToLocation( const std::shared_ptr<TextDocument>& doc, const std::string& search );
const PluginManager* getPluginManager() const;

View File

@@ -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<LSPSelectionRange> parent;
};
struct LSPParameterInformation {
// offsets into overall signature label
// (-1 if invalid)
int start;
int end;
};
struct LSPSignatureInformation {
std::string label;
LSPMarkupContent documentation;
std::vector<LSPParameterInformation> parameters;
};
struct LSPSignatureHelp {
std::vector<LSPSignatureInformation> 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<Int64>(), data["character"].get<Int64>() };
return {};
}
};
} // namespace ecode
#endif // ECODE_LSPCLIENTPROTOCOL_HPP

View File

@@ -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<void( const Notification& )> 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<PluginRequestHandle( const PluginMessage& )> cb ) const {
const_cast<PluginManager*>( 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<PluginManager*>( 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 } );
}

View File

@@ -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<const nlohmann::json*>( data ); }
bool isJSON() const { return format == PluginMessageFormat::JSON; }
const LSPPublishDiagnosticsParams& asDiagnostics() const {
return *static_cast<const LSPPublishDiagnosticsParams*>( data );
}
const std::vector<LSPCompletionItem>& asCodeCompletion() const {
return *static_cast<const std::vector<LSPCompletionItem>*>( data );
}
const LSPClientPlugin* asLSPClientPlugin() const {
return static_cast<const LSPClientPlugin*>( 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<nlohmann::json*>( data ); }
LSPPublishDiagnosticsParams& asDiagnostics() const {
return *static_cast<LSPPublishDiagnosticsParams*>( 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<void( const Notification& )> 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<PluginRequestHandle( const PluginMessage& )> cb ) const;
void unsubscribeMessages( UICodeEditorPlugin* plugin ) const;
protected:
friend class App;
@@ -135,14 +194,16 @@ class PluginManager {
std::map<std::string, PluginDefinition> mDefinitions;
std::shared_ptr<ThreadPool> mThreadPool;
UICodeEditorSplitter* mSplitter{ nullptr };
std::map<std::string, std::function<void( const Notification& )>> mSubscribedPlugins;
std::map<std::string, std::function<PluginRequestHandle( const PluginMessage& )>>
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 {