ecode: LinterPlugin now supports LSP diagnostics. LSP client plugin refactor.

This commit is contained in:
Martín Lucas Golini
2022-11-07 01:34:55 -03:00
parent eded28f70b
commit 4306e8018f
17 changed files with 508 additions and 162 deletions

View File

@@ -89,6 +89,27 @@ void LinterPlugin::loadLinterConfig( const std::string& path ) {
auto& config = j["config"];
if ( config.contains( "delay_time" ) )
setDelayTime( Time::fromString( config["delay_time"].get<std::string>() ) );
if ( config.contains( "enable_lsp_diagnostics" ) &&
config["enable_lsp_diagnostics"].is_boolean() )
setEnableLSPDiagnostics( config["enable_lsp_diagnostics"].get<bool>() );
if ( config.contains( "disable_lsp_languages" ) &&
config["disable_lsp_languages"].is_array() ) {
const auto& langs = config["disable_lsp_languages"];
try {
mLSPLanguagesDisabled.clear();
for ( const auto& lang : langs ) {
if ( lang.is_string() ) {
std::string lg = lang.get<std::string>();
if ( !lg.empty() )
mLSPLanguagesDisabled.insert( lg );
}
}
} catch ( const json::exception& e ) {
Log::debug(
"LinterPlugin::loadLinterConfig: Error parsing disable_lsp_languages: %s",
e.what() );
}
}
}
if ( !j.contains( "linters" ) )
@@ -166,7 +187,107 @@ void LinterPlugin::loadLinterConfig( const std::string& path ) {
}
}
LinterType getLinterTypeFromSeverity( const LSPDiagnosticSeverity& severity ) {
switch ( severity ) {
case LSPDiagnosticSeverity::Error:
return LinterType::Error;
case LSPDiagnosticSeverity::Warning:
return LinterType::Warning;
default:
return LinterType::Notice;
}
}
void LinterPlugin::eraseMatchesFromOrigin( TextDocument* doc, const MatchOrigin& origin ) {
Lock matchesLock( mMatchesMutex );
auto& docMatches = mMatches[doc];
std::vector<Int64> emptyMatchLines;
for ( auto& matchLine : docMatches ) {
bool found;
do {
found = false;
auto it = matchLine.second.begin();
for ( ; it != matchLine.second.end(); ++it ) {
if ( it->origin == origin ) {
found = true;
break;
}
}
if ( found ) {
matchLine.second.erase( it );
if ( matchLine.second.empty() )
emptyMatchLines.push_back( matchLine.first );
}
} while ( found );
}
for ( const auto& line : emptyMatchLines )
docMatches.erase( line );
}
void LinterPlugin::insertMatches( TextDocument* doc,
std::map<Int64, std::vector<LinterMatch>>& matches ) {
Lock matchesLock( mMatchesMutex );
auto& docMatches = mMatches[doc];
for ( auto& match : matches ) {
std::vector<LinterMatch>& vec = docMatches[match.first];
vec.insert( vec.end(), match.second.begin(), match.second.end() );
}
}
void LinterPlugin::setMatches( TextDocument* doc, const MatchOrigin& origin,
std::map<Int64, std::vector<LinterMatch>>& matches ) {
if ( !mEnableLSPDiagnostics ) {
Lock matchesLock( mMatchesMutex );
mMatches[doc] = std::move( matches );
} else {
eraseMatchesFromOrigin( doc, origin );
insertMatches( doc, matches );
}
invalidateEditors( doc );
}
void LinterPlugin::processNotification( const PluginManager::Notification& notification ) {
if ( !mEnableLSPDiagnostics ||
notification.type != PluginManager::NotificationType::PublishDiagnostics ||
notification.format != PluginManager::NotificationFormat::Diagnostics )
return;
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;
std::map<Int64, std::vector<LinterMatch>> matches;
for ( const auto& diag : diags.diagnostics ) {
LinterMatch match;
match.range = diag.range;
match.text = diag.message;
match.type = getLinterTypeFromSeverity( diag.severity );
match.lineCache = doc->line( match.range.start().line() ).getHash();
match.origin = MatchOrigin::Diagnostics;
matches[match.range.start().line()].emplace_back( std::move( match ) );
}
setMatches( doc, MatchOrigin::Diagnostics, matches );
}
TextDocument* LinterPlugin::getDocumentFromURI( const URI& uri ) {
for ( TextDocument* doc : mDocs ) {
if ( doc->getURI() == uri )
return doc;
}
return nullptr;
}
void LinterPlugin::load( const PluginManager* pluginManager ) {
pluginManager->subscribeNotifications(
this, [&]( const auto& notification ) { processNotification( notification ); } );
std::vector<std::string> paths;
std::string path( pluginManager->getResourcesPath() + "plugins/linters.json" );
if ( FileSystem::fileExists( path ) )
@@ -274,6 +395,14 @@ void LinterPlugin::setDelayTime( const Time& delayTime ) {
mDelayTime = delayTime;
}
bool LinterPlugin::getEnableLSPDiagnostics() const {
return mEnableLSPDiagnostics;
}
void LinterPlugin::setEnableLSPDiagnostics( bool enableLSPDiagnostics ) {
mEnableLSPDiagnostics = enableLSPDiagnostics;
}
void LinterPlugin::lintDoc( std::shared_ptr<TextDocument> doc ) {
ScopedOp op(
[&]() {
@@ -395,13 +524,35 @@ void LinterPlugin::runLinter( std::shared_ptr<TextDocument> doc, const Linter& l
if ( linter.columnsStartAtZero )
col++;
}
linterMatch.pos = { line - 1, col > 0 ? col - 1 : 0 };
linterMatch.lineCache = doc->line( line - 1 ).getHash();
linterMatch.range.setStart( { line - 1, col > 0 ? col - 1 : 0 } );
const String& text = doc->line( linterMatch.range.start().line() ).getText();
size_t minCol =
text.find_first_not_of( " \t\f\v\n\r", linterMatch.range.start().column() );
if ( minCol == String::InvalidPos )
minCol = linterMatch.range.start().column();
minCol = std::max( (Int64)minCol, linterMatch.range.start().column() );
if ( minCol >= text.size() )
minCol = linterMatch.range.start().column();
if ( minCol >= text.size() )
minCol = text.size() - 1;
linterMatch.range.setStart(
{ linterMatch.range.start().line(), (Int64)minCol } );
TextPosition endPos;
endPos = ( minCol < text.size() - 1 )
? doc->nextWordBoundary(
{ linterMatch.range.start().line(), (Int64)minCol } )
: doc->previousWordBoundary(
{ linterMatch.range.start().line(), (Int64)minCol } );
linterMatch.range.setEnd( endPos );
linterMatch.range = linterMatch.range.normalized();
linterMatch.lineCache = doc->line( linterMatch.range.start().line() ).getHash();
bool skip = false;
if ( linter.deduplicate && matches.find( line - 1 ) != matches.end() ) {
for ( auto& match : matches[line - 1] ) {
if ( match.pos == linterMatch.pos ) {
if ( match.range == linterMatch.range ) {
match.text += "\n" + linterMatch.text;
skip = true;
break;
@@ -433,12 +584,7 @@ void LinterPlugin::runLinter( std::shared_ptr<TextDocument> doc, const Linter& l
}
}
{
Lock matchesLock( mMatchesMutex );
mMatches[doc.get()] = std::move( matches );
}
invalidateEditors( doc.get() );
setMatches( doc.get(), MatchOrigin::Linter, matches );
Log::info( "LinterPlugin::runLinter for %s took %.2fms. Found: %d matches. Errors: %d, "
"Warnings: %d, Notices: %d.",
@@ -481,33 +627,9 @@ void LinterPlugin::drawAfterLineText( UICodeEditor* editor, const Int64& index,
line.setStyleConfig( editor->getFontStyleConfig() );
line.setColor(
editor->getColorScheme().getEditorSyntaxStyle( getMatchString( match.type ) ).color );
const String& text = doc->line( index ).getText();
size_t minCol = text.find_first_not_of( " \t\f\v\n\r", match.pos.column() );
if ( minCol == String::InvalidPos )
minCol = match.pos.column();
minCol = std::max( (Int64)minCol, match.pos.column() );
if ( minCol >= text.size() )
minCol = match.pos.column();
if ( minCol >= text.size() )
minCol = text.size() - 1;
Int64 strSize = 0;
TextPosition endPos;
Vector2f pos;
if ( minCol < text.size() - 1 ) {
endPos = doc->nextWordBoundary( { match.pos.line(), (Int64)minCol } );
strSize = eemax( (Int64)0, static_cast<Int64>( endPos.column() - minCol ) );
pos = { position.x + editor->getXOffsetCol( { match.pos.line(), (Int64)minCol } ),
position.y };
} else {
endPos = doc->previousWordBoundary( { match.pos.line(), (Int64)minCol } );
strSize = eemax( (Int64)0, static_cast<Int64>( minCol - endPos.column() ) );
pos = { position.x +
editor->getXOffsetCol( { match.pos.line(), (Int64)endPos.column() } ),
position.y };
}
Int64 strSize = match.range.end().column() - match.range.start().column();
Vector2f pos = { position.x + editor->getXOffsetCol( match.range.start() ), position.y };
if ( strSize == 0 ) {
strSize = 1;
pos = { position.x, position.y };

View File

@@ -16,6 +16,8 @@ namespace ecode {
enum class LinterType { Notice, Warning, Error };
enum class MatchOrigin { Linter, Diagnostics };
struct Linter {
std::vector<std::string> files;
std::vector<std::string> warningPattern;
@@ -36,10 +38,11 @@ struct Linter {
struct LinterMatch {
std::string text;
TextPosition pos;
String::HashType lineCache;
std::map<UICodeEditor*, Rectf> box;
TextRange range;
LinterType type{ LinterType::Error };
String::HashType lineCache;
MatchOrigin origin{ MatchOrigin::Linter };
std::map<UICodeEditor*, Rectf> box;
};
class LinterPlugin : public UICodeEditorPlugin {
@@ -88,6 +91,10 @@ class LinterPlugin : public UICodeEditorPlugin {
void setDelayTime( const Time& delayTime );
bool getEnableLSPDiagnostics() const;
void setEnableLSPDiagnostics( bool enableLSPDiagnostics );
protected:
std::shared_ptr<ThreadPool> mPool;
std::vector<Linter> mLinters;
@@ -107,6 +114,8 @@ class LinterPlugin : public UICodeEditorPlugin {
bool mReady{ false };
bool mShuttingDown{ false };
bool mHoveringMatch{ false };
bool mEnableLSPDiagnostics{ true };
std::set<std::string> mLSPLanguagesDisabled;
LinterPlugin( const PluginManager* pluginManager );
@@ -130,6 +139,17 @@ class LinterPlugin : public UICodeEditorPlugin {
void loadLinterConfig( const std::string& path );
size_t linterFilePatternPosition( const std::vector<std::string>& patterns );
void processNotification( const PluginManager::Notification& notification );
TextDocument* getDocumentFromURI( const URI& uri );
void eraseMatchesFromOrigin( TextDocument* doc, const MatchOrigin& origin );
void insertMatches( TextDocument* doc, std::map<Int64, std::vector<LinterMatch>>& matches );
void setMatches( TextDocument* doc, const MatchOrigin& origin,
std::map<Int64, std::vector<LinterMatch>>& matches );
};
} // namespace ecode

View File

@@ -25,7 +25,7 @@ LSPClientPlugin::LSPClientPlugin( const PluginManager* pluginManager ) :
LSPClientPlugin::~LSPClientPlugin() {
mClosing = true;
Lock l( mDocMutex );
for ( const auto &editor : mEditors ) {
for ( const auto& editor : mEditors ) {
for ( auto& kb : mKeyBindings ) {
editor.first->getKeyBindings().removeCommandKeybind( kb.first );
if ( editor.first->hasDocument() )
@@ -47,11 +47,10 @@ void LSPClientPlugin::update( UICodeEditor* ) {
mClientManager.updateDirty();
}
void LSPClientPlugin::processNotification( PluginManager::Notification noti,
const nlohmann::json& obj ) {
switch ( noti ) {
void LSPClientPlugin::processNotification( const PluginManager::Notification& notification ) {
switch ( notification.type ) {
case PluginManager::WorkspaceFolderChanged: {
mClientManager.didChangeWorkspaceFolders( obj["folder"] );
mClientManager.didChangeWorkspaceFolders( notification.asJSON()["folder"] );
break;
}
default:
@@ -61,7 +60,7 @@ void LSPClientPlugin::processNotification( PluginManager::Notification noti,
void LSPClientPlugin::load( const PluginManager* pluginManager ) {
pluginManager->subscribeNotifications(
this, [&]( auto noti, auto obj ) { processNotification( noti, obj ); } );
this, [&]( const auto& notification ) { processNotification( notification ); } );
std::vector<std::string> paths;
std::string path( pluginManager->getResourcesPath() + "plugins/lspclient.json" );
if ( FileSystem::fileExists( path ) )
@@ -137,6 +136,28 @@ void LSPClientPlugin::loadLSPConfig( std::vector<LSPDefinition>& lsps, const std
auto& servers = j["servers"];
for ( auto& obj : servers ) {
// Allow disabling an LSP by redeclaring it in the user configuration file.
if ( obj.contains( "name" ) && obj.contains( "disabled" ) &&
obj.at( "disabled" ).is_boolean() ) {
for ( auto& lsp : lsps ) {
if ( lsp.name == obj["name"] ) {
lsp.disabled = obj["disabled"].get<bool>();
break;
}
}
}
// Allow setting user command paramaters for an already declared LSP
if ( obj.contains( "name" ) && obj.contains( "command_parameters" ) &&
obj.at( "command_parameters" ).is_string() ) {
for ( auto& lsp : lsps ) {
if ( lsp.name == obj["name"] ) {
lsp.commandParameters = obj.value( "command_parameters", "" );
break;
}
}
}
if ( !obj.contains( "language" ) || !obj.contains( "file_patterns" ) ) {
Log::warning( "LSP server without language or file_patterns, ignored..." );
continue;
@@ -173,8 +194,8 @@ void LSPClientPlugin::loadLSPConfig( std::vector<LSPDefinition>& lsps, const std
lsp.name = obj["name"];
}
if ( obj.contains( "url" ) )
lsp.url = obj["url"];
lsp.url = obj.value( "url", "" );
lsp.commandParameters = obj.value( "command_parameters", lsp.commandParameters );
auto fp = obj["file_patterns"];

View File

@@ -92,7 +92,7 @@ class LSPClientPlugin : public UICodeEditorPlugin {
size_t lspFilePatternPosition( const std::vector<LSPDefinition>& lsps,
const std::vector<std::string>& patterns );
void processNotification( PluginManager::Notification, const nlohmann::json& );
void processNotification( const PluginManager::Notification& notification );
};
} // namespace ecode

View File

@@ -1,4 +1,5 @@
#include "lspclientserver.hpp"
#include "lspclientplugin.hpp"
#include "lspclientservermanager.hpp"
#include <algorithm>
#include <eepp/system/filesystem.hpp>
@@ -27,7 +28,7 @@ static const char* MEMBER_RESULT = "result";
static const char* MEMBER_START = "start";
static const char* MEMBER_END = "end";
static const char* MEMBER_POSITION = "position";
// static const char* MEMBER_POSITIONS = "positions";
static const char* MEMBER_POSITIONS = "positions";
static const char* MEMBER_LOCATION = "location";
static const char* MEMBER_RANGE = "range";
static const char* MEMBER_LINE = "line";
@@ -44,8 +45,8 @@ static const char* MEMBER_DIAGNOSTICS = "diagnostics";
static const char* MEMBER_TARGET_URI = "targetUri";
static const char* MEMBER_TARGET_RANGE = "targetRange";
static const char* MEMBER_TARGET_SELECTION_RANGE = "targetSelectionRange";
// static const char* MEMBER_PREVIOUS_RESULT_ID = "previousResultId";
// static const char* MEMBER_QUERY = "query";
static const char* MEMBER_PREVIOUS_RESULT_ID = "previousResultId";
static const char* MEMBER_QUERY = "query";
static json newRequest( const std::string& method, const json& params = json{} ) {
json j;
@@ -175,6 +176,20 @@ static json textDocumentPositionParams( const URI& document, TextPosition pos )
return params;
}
static json toJson( const std::vector<TextPosition>& positions ) {
json result;
for ( const auto& position : positions )
result.push_back( toJson( position ) );
return result;
}
static json textDocumentPositionsParams( const URI& document,
const std::vector<TextPosition>& positions ) {
auto params = textDocumentParams( document );
params[MEMBER_POSITIONS] = toJson( positions );
return params;
}
static void fromJson( std::vector<char>& trigger, const json& json ) {
if ( !json.empty() ) {
const auto triggersArray = json;
@@ -358,6 +373,37 @@ static std::vector<LSPSymbolInformation> parseDocumentSymbols( const json& resul
return ret;
}
static std::vector<LSPSymbolInformation> parseWorkspaceSymbols( const json& res ) {
std::vector<LSPSymbolInformation> symbols;
symbols.reserve( res.size() );
std::transform(
res.cbegin(), res.cend(), std::back_inserter( symbols ), []( const json& symbol ) {
LSPSymbolInformation symInfo;
const auto location = symbol.at( MEMBER_LOCATION );
const auto mrange = symbol.contains( MEMBER_RANGE ) ? symbol.at( MEMBER_RANGE )
: location.at( MEMBER_RANGE );
auto containerName = symbol.value( "containerName", "" );
if ( !containerName.empty() )
containerName.append( "::" );
symInfo.name = containerName + symbol.value( "name", "" );
symInfo.kind = (LSPSymbolKind)symbol.value( MEMBER_KIND, 1 );
symInfo.range = parseRange( mrange );
symInfo.url = URI( location.value( MEMBER_URI, "" ) );
symInfo.score = symbol.value( "score", 0.0 );
return symInfo;
} );
std::sort( symbols.begin(), symbols.end(),
[]( const LSPSymbolInformation& l, const LSPSymbolInformation& r ) {
return l.score > r.score;
} );
return symbols;
}
static LSPResponseError parseResponseError( const json& v ) {
LSPResponseError ret;
if ( v.is_object() ) {
@@ -611,6 +657,32 @@ static std::vector<LSPCompletionItem> parseDocumentCompletion( const json& resul
return ret;
}
static std::shared_ptr<LSPSelectionRange> parseSelectionRange( const json& selectionRange ) {
auto current = std::make_shared<LSPSelectionRange>( LSPSelectionRange{} );
std::shared_ptr<LSPSelectionRange> ret = current;
json selRange = std::move( selectionRange );
while ( selRange.is_object() ) {
current->range = parseRange( selRange[MEMBER_RANGE] );
if ( !selRange["parent"].is_object() ) {
current->parent = nullptr;
break;
}
selRange = selRange["parent"];
current->parent = std::make_shared<LSPSelectionRange>( LSPSelectionRange{} );
current = current->parent;
}
return ret;
}
static std::vector<std::shared_ptr<LSPSelectionRange>>
parseSelectionRanges( const json& selectionRanges ) {
std::vector<std::shared_ptr<LSPSelectionRange>> ret;
for ( const auto& selectionRange : selectionRanges )
ret.push_back( parseSelectionRange( selectionRange ) );
return ret;
}
void LSPClientServer::initialize() {
json codeAction{ { "codeActionLiteralSupport",
json{ { "codeActionKind", json{ { "valueSet", json::array() } } } } } };
@@ -735,7 +807,7 @@ LSPClientServer::RequestHandle LSPClientServer::cancel( int reqid ) {
LSPClientServer::RequestHandle LSPClientServer::write( const json& msg, const JsonReplyHandler& h,
const JsonReplyHandler& eh, const int id ) {
RequestHandle ret;
ret.mServer = this;
ret.server = this;
if ( !mProcess.isAlive() )
return ret;
@@ -746,7 +818,7 @@ LSPClientServer::RequestHandle LSPClientServer::write( const json& msg, const Js
// notification == no handler
if ( h ) {
ob[MEMBER_ID] = ++mLastMsgId;
ret.mId = mLastMsgId;
ret.id = mLastMsgId;
Lock l( mHandlersMutex );
mHandlers[mLastMsgId] = { h, eh };
} else if ( id ) {
@@ -907,6 +979,21 @@ LSPClientServer::documentSymbols( const URI& document,
} );
}
LSPClientServer::RequestHandle 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::workspaceSymbol( const std::string& querySymbol,
const SymbolInformationHandler& h ) {
return workspaceSymbol( querySymbol, [h]( const json& json ) {
if ( h )
h( parseWorkspaceSymbols( json ) );
} );
}
void fromJson( LSPWorkDoneProgressValue& value, const json& json ) {
if ( !json.empty() ) {
auto ob = json;
@@ -942,14 +1029,18 @@ static json newError( const LSPErrorCode& code, const std::string& msg ) {
}
void LSPClientServer::publishDiagnostics( const json& msg ) {
// should emmit event somewhere
auto res = parsePublishDiagnostics( msg[MEMBER_PARAMS] );
LSPPublishDiagnosticsParams res = parsePublishDiagnostics( msg[MEMBER_PARAMS] );
if ( mManager && mManager->getPluginManager() && mManager->getPlugin() ) {
mManager->getPluginManager()->pushNotification( mManager->getPlugin(),
PluginManager::PublishDiagnostics,
PluginManager::Diagnostics, &res );
}
Log::debug( "LSPClientServer::publishDiagnostics: %s - returned %zu items",
res.uri.toString().c_str(), res.diagnostics.size() );
}
void LSPClientServer::workDoneProgress( const LSPWorkDoneProgressParams& workDoneParams ) {
// should emmit event somewhere
// should emit event somewhere
Log::debug( "LSPClientServer::workDoneProgress: %s",
workDoneParams.token.is_string()
? workDoneParams.token.get<std::string>().c_str()
@@ -1187,11 +1278,6 @@ LSPClientServer::RequestHandle LSPClientServer::documentHover( const URI& docume
} );
}
LSPClientServer::RequestHandle LSPClientServer::documentHover( TextDocument* doc,
const HoverHandler& h ) {
return documentHover( doc->getURI(), doc->getSelection().start(), h );
}
LSPClientServer::RequestHandle LSPClientServer::documentCompletion( const URI& document,
const TextPosition& pos,
const JsonReplyHandler& h ) {
@@ -1208,4 +1294,38 @@ LSPClientServer::RequestHandle LSPClientServer::documentCompletion( const URI& d
} );
}
LSPClientServer::RequestHandle
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::selectionRange( const URI& document, const std::vector<TextPosition>& positions,
const SelectionRangeHandler& h ) {
return selectionRange( document, positions, [h]( const json& json ) {
if ( h )
h( parseSelectionRanges( json ) );
} );
}
LSPClientServer::RequestHandle
LSPClientServer::documentSemanticTokensFull( const URI& document, bool delta,
const std::string& requestId, const TextRange& range,
const JsonReplyHandler& h ) {
auto params = textDocumentParams( document );
// Delta
if ( delta && !requestId.empty() ) {
params[MEMBER_PREVIOUS_RESULT_ID] = requestId;
return send( newRequest( "textDocument/semanticTokens/full/delta", params ), h );
}
// Range
if ( range.isValid() ) {
params[MEMBER_RANGE] = toJson( range );
return send( newRequest( "textDocument/semanticTokens/range", params ), h );
}
return send( newRequest( "textDocument/semanticTokens/full", params ), h );
}
} // namespace ecode

View File

@@ -1,7 +1,7 @@
#ifndef ECODE_LSPCLIENTSERVER_HPP
#define ECODE_LSPCLIENTSERVER_HPP
#include "lspclientprotocol.hpp"
#include "lspprotocol.hpp"
#include "lspdefinition.hpp"
#include "lspdocumentclient.hpp"
#include <eepp/system/process.hpp>
@@ -31,16 +31,19 @@ class LSPClientServer {
using CodeActionHandler = ReplyHandler<std::vector<LSPCodeAction>>;
using HoverHandler = ReplyHandler<LSPHover>;
using CompletionHandler = ReplyHandler<std::vector<LSPCompletionItem>>;
using SymbolInformationHandler = ReplyHandler<std::vector<LSPSymbolInformation>>;
using SelectionRangeHandler = ReplyHandler<std::vector<std::shared_ptr<LSPSelectionRange>>>;
class RequestHandle {
private:
friend class LSPClientServer;
LSPClientServer* mServer;
int mId = 0;
LSPClientServer* server;
int id = 0;
public:
RequestHandle& cancel() {
if ( mServer )
mServer->cancel( mId );
if ( server )
server->cancel( id );
return *this;
}
};
@@ -60,51 +63,45 @@ class LSPClientServer {
const std::shared_ptr<ThreadPool>& getThreadPool() const;
LSPClientServer::RequestHandle cancel( int id );
RequestHandle cancel( int id );
LSPClientServer::RequestHandle send( const json& msg, const JsonReplyHandler& h = nullptr,
const JsonReplyHandler& eh = nullptr );
RequestHandle send( const json& msg, const JsonReplyHandler& h = nullptr,
const JsonReplyHandler& eh = nullptr );
const LSPDefinition& getDefinition() const { return mLSP; }
LSPClientServer::RequestHandle documentSymbols( const URI& document, const JsonReplyHandler& h,
const JsonReplyHandler& eh );
RequestHandle documentSymbols( const URI& document, const JsonReplyHandler& h,
const JsonReplyHandler& eh );
LSPClientServer::RequestHandle
documentSymbols( const URI& document, const ReplyHandler<std::vector<LSPSymbolInformation>>& h,
const ReplyHandler<LSPResponseError>& eh );
RequestHandle documentSymbols( const URI& document,
const ReplyHandler<std::vector<LSPSymbolInformation>>& h,
const ReplyHandler<LSPResponseError>& eh );
LSPClientServer::RequestHandle didOpen( const URI& document, const std::string& text,
int version );
RequestHandle didOpen( const URI& document, const std::string& text, int version );
LSPClientServer::RequestHandle didOpen( TextDocument* doc, int version );
RequestHandle didOpen( TextDocument* doc, int version );
LSPClientServer::RequestHandle didSave( const URI& document, const std::string& text );
RequestHandle didSave( const URI& document, const std::string& text );
LSPClientServer::RequestHandle didSave( TextDocument* doc );
RequestHandle didSave( TextDocument* doc );
LSPClientServer::RequestHandle didClose( const URI& document );
RequestHandle didClose( const URI& document );
LSPClientServer::RequestHandle didClose( TextDocument* document );
RequestHandle didClose( TextDocument* document );
LSPClientServer::RequestHandle
didChange( const URI& document, int version, const std::string& text,
const std::vector<DocumentContentChange>& change = {} );
RequestHandle didChange( const URI& document, int version, const std::string& text,
const std::vector<DocumentContentChange>& change = {} );
LSPClientServer::RequestHandle
didChange( TextDocument* doc, const std::vector<DocumentContentChange>& change = {} );
RequestHandle didChange( TextDocument* doc,
const std::vector<DocumentContentChange>& change = {} );
LSPClientServer::RequestHandle documentDefinition( const URI& document,
const TextPosition& pos );
RequestHandle documentDefinition( const URI& document, const TextPosition& pos );
LSPClientServer::RequestHandle documentDeclaration( const URI& document,
const TextPosition& pos );
RequestHandle documentDeclaration( const URI& document, const TextPosition& pos );
LSPClientServer::RequestHandle documentTypeDefinition( const URI& document,
const TextPosition& pos );
RequestHandle documentTypeDefinition( const URI& document, const TextPosition& pos );
LSPClientServer::RequestHandle documentImplementation( const URI& document,
const TextPosition& pos );
RequestHandle documentImplementation( const URI& document, const TextPosition& pos );
void updateDirty();
@@ -112,42 +109,54 @@ class LSPClientServer {
bool hasDocuments() const;
LSPClientServer::RequestHandle
didChangeWorkspaceFolders( const std::vector<LSPWorkspaceFolder>& added,
const std::vector<LSPWorkspaceFolder>& removed );
RequestHandle didChangeWorkspaceFolders( const std::vector<LSPWorkspaceFolder>& added,
const std::vector<LSPWorkspaceFolder>& removed );
void publishDiagnostics( const json& msg );
void workDoneProgress( const LSPWorkDoneProgressParams& workDoneParams );
LSPClientServer::RequestHandle getAndGoToLocation( const URI& document, const TextPosition& pos,
const std::string& search );
RequestHandle getAndGoToLocation( const URI& document, const TextPosition& pos,
const std::string& search );
LSPClientServer::RequestHandle switchSourceHeader( const URI& document );
RequestHandle switchSourceHeader( const URI& document );
LSPClientServer::RequestHandle 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 JsonReplyHandler& h );
LSPClientServer::RequestHandle documentCodeAction( const URI& document, const TextRange& range,
const std::vector<std::string>& kinds,
std::vector<LSPDiagnostic> diagnostics,
const CodeActionHandler& h );
RequestHandle documentCodeAction( const URI& document, const TextRange& range,
const std::vector<std::string>& kinds,
std::vector<LSPDiagnostic> diagnostics,
const CodeActionHandler& h );
LSPClientServer::RequestHandle documentHover( const URI& document, const TextPosition& pos,
const JsonReplyHandler& h );
RequestHandle documentHover( const URI& document, const TextPosition& pos,
const JsonReplyHandler& h );
LSPClientServer::RequestHandle documentHover( const URI& document, const TextPosition& pos,
const HoverHandler& h );
RequestHandle documentHover( const URI& document, const TextPosition& pos,
const HoverHandler& h );
LSPClientServer::RequestHandle documentHover( TextDocument* doc, const HoverHandler& h );
RequestHandle documentCompletion( const URI& document, const TextPosition& pos,
const JsonReplyHandler& h );
LSPClientServer::RequestHandle documentCompletion( const URI& document, const TextPosition& pos,
const JsonReplyHandler& h );
RequestHandle documentCompletion( const URI& document, const TextPosition& pos,
const CompletionHandler& h );
LSPClientServer::RequestHandle documentCompletion( const URI& document, const TextPosition& pos,
const CompletionHandler& h );
RequestHandle workspaceSymbol( const std::string& querySymbol, const JsonReplyHandler& h );
RequestHandle workspaceSymbol( const std::string& querySymbol,
const SymbolInformationHandler& h );
RequestHandle 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 );
RequestHandle documentSemanticTokensFull( const URI& document, bool delta,
const std::string& requestId, const TextRange& range,
const JsonReplyHandler& h );
protected:
LSPClientServerManager* mManager{ nullptr };
@@ -177,8 +186,8 @@ class LSPClientServer {
void readStdErr( const char* bytes, size_t n );
LSPClientServer::RequestHandle write( const json& msg, const JsonReplyHandler& h = nullptr,
const JsonReplyHandler& eh = nullptr, const int id = 0 );
RequestHandle write( const json& msg, const JsonReplyHandler& h = nullptr,
const JsonReplyHandler& eh = nullptr, const int id = 0 );
void initialize();

View File

@@ -11,6 +11,7 @@ LSPClientServerManager::LSPClientServerManager() {}
void LSPClientServerManager::load( LSPClientPlugin* plugin, const PluginManager* pluginManager,
std::vector<LSPDefinition>&& lsps ) {
mPlugin = plugin;
mPluginManager = pluginManager;
mThreadPool = pluginManager->getThreadPool();
mLSPs = lsps;
}
@@ -168,6 +169,14 @@ void LSPClientServerManager::getAndGoToLocation( const std::shared_ptr<TextDocum
server->getAndGoToLocation( doc->getURI(), doc->getSelection().start(), search );
}
const PluginManager* LSPClientServerManager::getPluginManager() const {
return mPluginManager;
}
LSPClientPlugin* LSPClientServerManager::getPlugin() const {
return mPlugin;
}
void LSPClientServerManager::didChangeWorkspaceFolders( const std::string& folder ) {
mLSPWorkspaceFolder = { "file://" + folder, FileSystem::fileNameFromPath( folder ) };
Lock l( mClientsMutex );

View File

@@ -47,9 +47,13 @@ class LSPClientServerManager {
void getAndGoToLocation( const std::shared_ptr<TextDocument>& doc, const std::string& search );
const PluginManager* getPluginManager() const;
LSPClientPlugin* getPlugin() const;
protected:
friend class LSPClientServer;
const PluginManager* mPluginManager{ nullptr };
LSPClientPlugin* mPlugin{ nullptr };
std::shared_ptr<ThreadPool> mThreadPool;
std::map<String::HashType, std::unique_ptr<LSPClientServer>> mClients;

View File

@@ -10,8 +10,10 @@ struct LSPDefinition {
std::string name;
std::vector<std::string> filePatterns;
std::string command;
std::string commandParameters;
std::vector<std::string> rootIndicationFileNames;
std::string url;
bool disabled{ false };
};
} // namespace ecode

View File

@@ -9,6 +9,9 @@ using namespace EE;
using namespace EE::UI::Doc;
using namespace EE::Network;
// The LSP protocol will be consumed by any plugin, and it's not exclusive to the LSP plugin
// The protocol should be taken as a reference on how to implement the comunication between plugins
namespace ecode {
enum class LSPErrorCode {
@@ -219,10 +222,6 @@ enum class LSPSymbolKind {
TypeParameter = 26,
};
enum class LSPSymbolTag : Uint8 {
Deprecated = 1,
};
struct LSPSymbolInformation {
LSPSymbolInformation() = default;
LSPSymbolInformation( const std::string& _name, LSPSymbolKind _kind, TextRange _range,
@@ -230,12 +229,11 @@ struct LSPSymbolInformation {
name( _name ), detail( _detail ), kind( _kind ), range( _range ) {}
std::string name;
std::string detail;
LSPSymbolKind kind;
LSPSymbolKind kind{ LSPSymbolKind::File };
URI url;
TextRange range;
TextRange selectionRange;
double score = 0.0;
LSPSymbolTag tags;
std::vector<LSPSymbolInformation> children;
};
@@ -259,7 +257,6 @@ struct LSPHover {
TextRange range;
};
enum class LSPCompletionItemKind {
Text = 1,
Method = 2,
@@ -300,6 +297,11 @@ struct LSPCompletionItem {
std::vector<LSPTextEdit> additionalTextEdits;
};
struct LSPSelectionRange {
TextRange range;
std::shared_ptr<LSPSelectionRange> parent;
};
} // namespace ecode
#endif // ECODE_LSPCLIENTPROTOCOL_HPP

View File

@@ -104,23 +104,24 @@ const std::string& PluginManager::getWorkspaceFolder() const {
void PluginManager::setWorkspaceFolder( const std::string& workspaceFolder ) {
mWorkspaceFolder = workspaceFolder;
sendNotification( Notification::WorkspaceFolderChanged,
json{ { "folder", mWorkspaceFolder } } );
json data{ { "folder", mWorkspaceFolder } };
sendNotification( NotificationType::WorkspaceFolderChanged, NotificationFormat::JSON, &data );
}
void PluginManager::pushNotification( UICodeEditorPlugin* pluginWho, Notification notification,
const nlohmann::json& json ) const {
void PluginManager::pushNotification( UICodeEditorPlugin* pluginWho, NotificationType notification,
NotificationFormat format, void* data ) const {
for ( const auto& plugin : mSubscribedPlugins )
if ( pluginWho->getId() != plugin.first )
plugin.second( notification, json );
plugin.second( { notification, format, data } );
}
void PluginManager::subscribeNotifications(
UICodeEditorPlugin* plugin,
std::function<void( Notification, const nlohmann::json& )> cb ) const {
void PluginManager::subscribeNotifications( UICodeEditorPlugin* plugin,
std::function<void( const Notification& )> cb ) const {
const_cast<PluginManager*>( this )->mSubscribedPlugins[plugin->getId()] = cb;
if ( !mWorkspaceFolder.empty() )
cb( Notification::WorkspaceFolderChanged, json{ { "folder", mWorkspaceFolder } } );
if ( !mWorkspaceFolder.empty() ) {
json data{ { "folder", mWorkspaceFolder } };
cb( { NotificationType::WorkspaceFolderChanged, NotificationFormat::JSON, &data } );
}
}
void PluginManager::unsubscribeNotifications( UICodeEditorPlugin* plugin ) const {
@@ -131,10 +132,10 @@ void PluginManager::setSplitter( UICodeEditorSplitter* splitter ) {
mSplitter = splitter;
}
void PluginManager::sendNotification( const Notification& notification,
const nlohmann::json& json ) {
void PluginManager::sendNotification( const NotificationType& notification,
const NotificationFormat& format, void* data ) {
for ( const auto& plugin : mSubscribedPlugins )
plugin.second( notification, json );
plugin.second( { notification, format, data } );
}
bool PluginManager::hasDefinition( const std::string& id ) {

View File

@@ -1,6 +1,7 @@
#ifndef ECODE_PLUGINMANAGER_HPP
#define ECODE_PLUGINMANAGER_HPP
#include "lsp/lspprotocol.hpp"
#include <eepp/ui/models/model.hpp>
#include <eepp/ui/tools/uicodeeditorsplitter.hpp>
#include <eepp/ui/uicodeeditor.hpp>
@@ -57,7 +58,19 @@ struct PluginDefinition {
class PluginManager {
public:
enum Notification { WorkspaceFolderChanged };
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 ) );
@@ -104,12 +117,11 @@ class PluginManager {
void setWorkspaceFolder( const std::string& workspaceFolder );
void pushNotification( UICodeEditorPlugin* pluginWho, Notification,
const nlohmann::json& ) const;
void pushNotification( UICodeEditorPlugin* pluginWho, NotificationType, NotificationFormat,
void* data ) const;
void
subscribeNotifications( UICodeEditorPlugin* plugin,
std::function<void( Notification, const nlohmann::json& )> cb ) const;
void subscribeNotifications( UICodeEditorPlugin* plugin,
std::function<void( const Notification& )> cb ) const;
void unsubscribeNotifications( UICodeEditorPlugin* plugin ) const;
@@ -123,14 +135,14 @@ class PluginManager {
std::map<std::string, PluginDefinition> mDefinitions;
std::shared_ptr<ThreadPool> mThreadPool;
UICodeEditorSplitter* mSplitter{ nullptr };
std::map<std::string, std::function<void( Notification, const nlohmann::json& )>>
mSubscribedPlugins;
std::map<std::string, std::function<void( const Notification& )>> mSubscribedPlugins;
bool hasDefinition( const std::string& id );
void setSplitter( UICodeEditorSplitter* splitter );
void sendNotification( const Notification& notification, const nlohmann::json& json = {} );
void sendNotification( const NotificationType& notification, const NotificationFormat& format,
void* data );
};
class PluginsModel : public Model {