Added Sys::getProcessID. Minor fixes.

ecode: LSP Client WIP. Some keybinding refactor.
This commit is contained in:
Martín Lucas Golini
2022-10-30 04:11:25 -03:00
parent 37e27ad670
commit 5e7903337b
37 changed files with 1133 additions and 520 deletions

View File

@@ -198,7 +198,7 @@ void FormatterPlugin::load( const PluginManager* pluginManager ) {
for ( const auto& path : paths ) {
try {
loadFormatterConfig( path );
} catch ( json::exception& e ) {
} catch ( const json::exception& e ) {
Log::error( "Parsing formatter \"%s\" failed:\n%s", path.c_str(), e.what() );
}
}

View File

@@ -95,11 +95,11 @@ void LinterPlugin::loadLinterConfig( const std::string& path ) {
auto& linters = j["linters"];
for ( auto& obj : linters ) {
Linter linter;
if ( !obj.contains( "file_patterns" ) || !obj.contains( "warning_pattern" ) ||
!obj.contains( "command" ) )
continue;
Linter linter;
auto fp = obj["file_patterns"];
for ( auto& pattern : fp )
@@ -181,7 +181,7 @@ void LinterPlugin::load( const PluginManager* pluginManager ) {
for ( const auto& path : paths ) {
try {
loadLinterConfig( path );
} catch ( json::exception& e ) {
} catch ( const json::exception& e ) {
Log::error( "Parsing linter \"%s\" failed:\n%s", path.c_str(), e.what() );
}
}
@@ -259,6 +259,8 @@ void LinterPlugin::update( UICodeEditor* editor ) {
mDirtyDoc.erase( doc.get() );
#if LINTER_THREADED
mPool->run( [&, doc] { lintDoc( doc ); }, [] {} );
#else
lintDoc( doc );
#endif
}
}

View File

@@ -1,7 +0,0 @@
#include "lspclient.hpp"
namespace ecode {
LSPClient::LSPClient() {}
} // namespace ecode

View File

@@ -1,25 +0,0 @@
#ifndef ECODE_LSPCLIENT_HPP
#define ECODE_LSPCLIENT_HPP
#include <eepp/system/process.hpp>
#include <eepp/ui/doc/textdocument.hpp>
#include <memory>
using namespace EE::System;
using namespace EE::UI;
using namespace EE::UI::Doc;
namespace ecode {
class LSPClient {
public:
static std::shared_ptr<LSPClient> get( const std::shared_ptr<TextDocument>& doc );
protected:
LSPClient();
};
} // namespace ecode
#endif // ECODE_LSPCLIENT_HPP

View File

@@ -1,13 +0,0 @@
#include "lspclientmanager.hpp"
namespace ecode {
LSPClientManager::LSPClientManager() {}
void LSPClientManager::load( const PluginManager* pluginManager ) {}
size_t LSPClientManager::clientCount() const {
return mClients.size();
}
} // namespace ecode

View File

@@ -1,26 +0,0 @@
#ifndef ECODE_LSPCLIENTMANAGER_HPP
#define ECODE_LSPCLIENTMANAGER_HPP
#include "../pluginmanager.hpp"
#include "lspclient.hpp"
#include <eepp/core.hpp>
using namespace EE;
namespace ecode {
class LSPClientManager {
public:
LSPClientManager();
void load( const PluginManager* pluginManager );
size_t clientCount() const;
protected:
std::map<String::HashType, std::unique_ptr<LSPClient>> mClients;
};
} // namespace ecode
#endif // ECODE_LSPCLIENTMANAGER_HPP

View File

@@ -0,0 +1,183 @@
#include "lspclientplugin.hpp"
#include <eepp/system/filesystem.hpp>
#include <eepp/system/lock.hpp>
#include <eepp/system/luapattern.hpp>
#include <eepp/ui/uiscenenode.hpp>
#include <nlohmann/json.hpp>
using json = nlohmann::json;
namespace ecode {
UICodeEditorPlugin* LSPClientPlugin::New( const PluginManager* pluginManager ) {
return eeNew( LSPClientPlugin, ( pluginManager ) );
}
LSPClientPlugin::LSPClientPlugin( const PluginManager* pluginManager ) :
mPool( pluginManager->getThreadPool() ) {
mPool->run( [&, pluginManager] { load( pluginManager ); }, [] {} );
}
LSPClientPlugin::~LSPClientPlugin() {
mClosing = true;
Lock l( mDocMutex );
for ( const auto& editor : mEditors ) {
editor.first->unregisterPlugin( this );
}
}
void LSPClientPlugin::load( const PluginManager* pluginManager ) {
std::vector<std::string> paths;
std::string path( pluginManager->getResourcesPath() + "plugins/lspclient.json" );
if ( FileSystem::fileExists( path ) )
paths.emplace_back( path );
path = pluginManager->getPluginsPath() + "lspclient.json";
if ( FileSystem::fileExists( path ) ||
FileSystem::fileWrite( path, "{\n\"config\":{},\n\"servers\":[]\n}\n" ) ) {
mConfigPath = path;
paths.emplace_back( path );
}
if ( paths.empty() )
return;
std::vector<LSPDefinition> lsps;
for ( const auto& path : paths ) {
try {
loadLSPConfig( lsps, path );
} catch ( const json::exception& e ) {
Log::error( "Parsing LSP \"%s\" failed:\n%s", path.c_str(), e.what() );
}
}
mClientManager.load( pluginManager, std::move( lsps ) );
mReady = mClientManager.clientCount() > 0;
if ( mReady )
fireReadyCbs();
}
void LSPClientPlugin::loadLSPConfig( std::vector<LSPDefinition>& lsps, const std::string& path ) {
std::string data;
if ( !FileSystem::fileGet( path, data ) )
return;
json j;
try {
j = json::parse( data, nullptr, true, true );
} catch ( ... ) {
return;
}
if ( !j.contains( "servers" ) )
return;
auto& servers = j["servers"];
for ( auto& obj : servers ) {
if ( !obj.contains( "language" ) || !obj.contains( "file_patterns" ) ) {
Log::warning( "LSP server without language or file_patterns, ignored..." );
continue;
}
if ( !obj.contains( "use" ) && !( obj.contains( "command" ) && obj.contains( "name" ) ) ) {
Log::warning( "LSP server without name+command or use, ignored..." );
continue;
}
LSPDefinition lsp;
if ( obj.contains( "use" ) ) {
std::string use = obj["use"];
bool foundTlsp = false;
for ( const auto& tlsp : lsps ) {
if ( tlsp.name == use ) {
lsp.language = obj["language"];
foundTlsp = true;
lsp.command = tlsp.command;
lsp.name = tlsp.name;
break;
}
}
if ( !foundTlsp ) {
Log::warning( "LSP server trying to use an undeclared LSP. Father LSP must be "
"declared first." );
continue;
}
} else {
lsp.language = obj["language"];
lsp.command = obj["command"];
lsp.name = obj["name"];
}
if ( obj.contains( "url" ) )
lsp.url = obj["url"];
auto fp = obj["file_patterns"];
for ( auto& pattern : fp )
lsp.filePatterns.push_back( pattern.get<std::string>() );
if ( obj.contains( "rootIndicationFileNames" ) ) {
auto fnms = obj["rootIndicationFileNames"];
for ( auto& fn : fnms )
lsp.rootIndicationFileNames.push_back( fn );
}
// If the file pattern is repeated, we will overwrite the previous LSP.
// The previous LSP should be the "default" LSP that comes with ecode.
size_t pos = lspFilePatternPosition( lsps, lsp.filePatterns );
if ( pos != std::string::npos ) {
lsps[pos] = lsp;
} else {
lsps.emplace_back( std::move( lsp ) );
}
}
}
size_t LSPClientPlugin::lspFilePatternPosition( const std::vector<LSPDefinition>& lsps,
const std::vector<std::string>& patterns ) {
for ( size_t i = 0; i < lsps.size(); ++i ) {
for ( const std::string& filePattern : lsps[i].filePatterns ) {
for ( const std::string& pattern : patterns ) {
if ( filePattern == pattern ) {
return i;
}
}
}
}
return std::string::npos;
}
void LSPClientPlugin::onRegister( UICodeEditor* editor ) {
Lock l( mDocMutex );
mDocs.insert( editor->getDocumentRef().get() );
std::vector<Uint32> listeners;
listeners.push_back(
editor->addEventListener( Event::OnDocumentLoaded, [&, editor]( const Event* ) {
mClientManager.run( editor->getDocumentRef() );
} ) );
mEditors.insert( { editor, listeners } );
mEditorDocs[editor] = editor->getDocumentRef().get();
if ( editor->hasDocument() && editor->getDocument().hasFilepath() )
mClientManager.run( editor->getDocumentRef() );
}
void LSPClientPlugin::onUnregister( UICodeEditor* editor ) {
if ( mClosing )
return;
Lock l( mDocMutex );
TextDocument* doc = mEditorDocs[editor];
auto cbs = mEditors[editor];
for ( auto listener : cbs )
editor->removeEventListener( listener );
mEditors.erase( editor );
mEditorDocs.erase( editor );
for ( auto editor : mEditorDocs )
if ( editor.second == doc )
return;
mDocs.erase( doc );
}
} // namespace ecode

View File

@@ -2,7 +2,7 @@
#define ECODE_LSPPLUGIN_HPP
#include "../pluginmanager.hpp"
#include "lspclientmanager.hpp"
#include "lspclientservermanager.hpp"
#include <eepp/config.hpp>
#include <eepp/system/clock.hpp>
#include <eepp/system/mutex.hpp>
@@ -17,26 +17,21 @@ using namespace EE::UI;
namespace ecode {
struct LSP {
std::string name;
std::string language;
const SyntaxDefinition* langDefinition;
std::vector<SyntaxPattern> filePatterns;
std::string command;
std::string url;
std::vector<std::string> rootIndicationFileName;
};
class LSPPlugin : public UICodeEditorPlugin {
// Implementation of the LSP Client:
// https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/
class LSPClientPlugin : public UICodeEditorPlugin {
public:
static PluginDefinition Definition() {
return {
"lsp", "LSP Client", "Language Server Protocol Client.", LSPPlugin::New, { 0, 1, 0 } };
return { "lspclient",
"LSP Client",
"Language Server Protocol Client.",
LSPClientPlugin::New,
{ 0, 0, 1 } };
}
static UICodeEditorPlugin* New( const PluginManager* pluginManager );
virtual ~LSPPlugin();
virtual ~LSPClientPlugin();
std::string getId() { return Definition().id; }
@@ -57,16 +52,19 @@ class LSPPlugin : public UICodeEditorPlugin {
std::unordered_map<UICodeEditor*, std::vector<Uint32>> mEditors;
std::set<TextDocument*> mDocs;
std::unordered_map<UICodeEditor*, TextDocument*> mEditorDocs;
LSPClientManager mClientManager;
LSPClientServerManager mClientManager;
std::string mConfigPath;
bool mClosing{ false };
bool mReady{ false };
LSPPlugin( const PluginManager* pluginManager );
LSPClientPlugin( const PluginManager* pluginManager );
void load( const PluginManager* pluginManager );
void loadLSPConfig( const std::string& path );
void loadLSPConfig( std::vector<LSPDefinition>& lsps, const std::string& path );
size_t lspFilePatternPosition( const std::vector<LSPDefinition>& lsps,
const std::vector<std::string>& patterns );
};
} // namespace ecode

View File

@@ -0,0 +1,238 @@
#include "lspclientserver.hpp"
#include <eepp/system/log.hpp>
#include <eepp/system/sys.hpp>
#include <eepp/ui/doc/textdocument.hpp>
namespace ecode {
static const char* MEMBER_ID = "id";
static const char* MEMBER_METHOD = "method";
static const char* MEMBER_PARAMS = "params";
static const char* MEMBER_URI = "uri";
static const char* MEMBER_VERSION = "version";
static const char* MEMBER_TEXT = "text";
static const char* MEMBER_LANGID = "languageId";
// static const char* MEMBER_ERROR = "error";
// static const char* MEMBER_CODE = "code";
// static const char* MEMBER_MESSAGE = "message";
// 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_LOCATION = "location";
// static const char* MEMBER_RANGE = "range";
// static const char* MEMBER_LINE = "line";
// static const char* MEMBER_CHARACTER = "character";
// static const char* MEMBER_KIND = "kind";
// static const char* MEMBER_LABEL = "label";
// static const char* MEMBER_DOCUMENTATION = "documentation";
// static const char* MEMBER_DETAIL = "detail";
// static const char* MEMBER_COMMAND = "command";
// static const char* MEMBER_EDIT = "edit";
// static const char* MEMBER_TITLE = "title";
// static const char* MEMBER_ARGUMENTS = "arguments";
// 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 json newRequest( const std::string& method, const json& params ) {
json j;
j[MEMBER_METHOD] = method;
j[MEMBER_PARAMS] = params;
return j;
}
static json versionedTextDocumentIdentifier( const URI& document, int version = -1 ) {
json map{ { MEMBER_URI, document.toString() } };
if ( version >= 0 )
map[MEMBER_VERSION] = version;
return map;
}
static json textDocumentItem( const URI& document, const std::string& lang, const std::string& text,
int version ) {
auto map = versionedTextDocumentIdentifier( document, version );
map[MEMBER_TEXT] = text;
map[MEMBER_LANGID] = lang;
return map;
}
static json textDocumentParams( const json& m ) {
return json{ { "textDocument", m } };
}
static json textDocumentParams( const URI& document, int version = -1 ) {
return textDocumentParams( versionedTextDocumentIdentifier( document, version ) );
}
LSPClientServer::LSPClientServer( const LSPDefinition& lsp, const std::string& rootPath ) :
mLSP( lsp ), mRootPath( rootPath ) {}
LSPClientServer::~LSPClientServer() {
for ( const auto& client : mClients ) {
client.first->unregisterClient( client.second.get() );
}
}
bool LSPClientServer::start() {
bool ret = mProcess.create( mLSP.command, Process::getDefaultOptions(), {}, mRootPath );
if ( ret ) {
mProcess.startAsyncRead(
[this]( const char* bytes, size_t n ) { readStdOut( bytes, n ); },
[this]( const char* bytes, size_t n ) { readStdErr( bytes, n ); } );
initialize();
}
return ret;
}
bool LSPClientServer::registerDoc( const std::shared_ptr<TextDocument>& doc ) {
for ( auto& cdoc : mDocs ) {
if ( cdoc.get() == doc.get() ) {
if ( mClients.find( doc.get() ) == mClients.end() ) {
mClients[doc.get()] = std::make_unique<LSPDocumentClient>( this, doc.get() );
return true;
}
return false;
}
}
mClients[doc.get()] = std::make_unique<LSPDocumentClient>( this, doc.get() );
mDocs.emplace_back( doc );
doc->registerClient( mClients[doc.get()].get() );
return true;
}
LSPClientServer::RequestHandle LSPClientServer::write( const json& msg,
const GenericReplyHandler& h,
const GenericReplyHandler& eh,
const int id ) {
RequestHandle ret;
ret.mServer = this;
if ( !mProcess.isAlive() )
return ret;
auto ob = msg;
ob["jsonrpc"] = "2.0";
// notification == no handler
if ( h ) {
ob[MEMBER_ID] = ++mLastMsgId;
ret.mId = mLastMsgId;
mHandlers[mLastMsgId] = { h, eh };
} else if ( id ) {
ob[MEMBER_ID] = id;
}
std::string sjson = ob.dump();
sjson = String::format( "Content-Length: %lu\r\n\r\n%s", sjson.length(), sjson.c_str() );
Log::info( "LSPClient calling %s", msg["method"].get<std::string>().c_str() );
Log::debug( "LSPClient sending message:\n%s", sjson.c_str() );
mProcess.write( sjson );
return ret;
}
LSPClientServer::RequestHandle LSPClientServer::send( const json& msg, const GenericReplyHandler& h,
const GenericReplyHandler& eh ) {
if ( mProcess.isAlive() ) {
return write( msg, h, eh );
} else {
Log::error( "LSPClientServer - Send for non-running server: %s - %s", mLSP.name,
mLSP.language );
}
return RequestHandle();
}
LSPClientServer::RequestHandle 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::documentSymbols( const URI& document,
const GenericReplyHandler& h,
const GenericReplyHandler& eh ) {
auto params = textDocumentParams( document );
return send( newRequest( "textDocument/documentSymbol", params ), h, eh );
}
void LSPClientServer::readStdOut( const char* bytes, size_t /*n*/ ) {
const char* skipLength = strstr( bytes, "\r\n\r\n" );
if ( nullptr != skipLength ) {
try {
auto j = json::parse( skipLength + 4 );
Log::debug( "LSP Server %s said: \n%s", mLSP.name.c_str(), j.dump( 2, ' ' ).c_str() );
return;
} catch ( const json::exception& e ) {
Log::debug( "LSP Server %s said: Coudln't parse json err: %s", mLSP.name.c_str(),
e.what() );
}
}
Log::debug( "LSP Server %s said: \n%s", mLSP.name.c_str(), bytes );
}
void LSPClientServer::readStdErr( const char* bytes, size_t /*n*/ ) {
Log::debug( "LSP Server %s err said: \n%s", mLSP.name.c_str(), bytes );
}
static std::vector<std::string> supportedSemanticTokenTypes() {
return { ( "namespace" ), ( "type" ), ( "class" ), ( "enum" ),
( "interface" ), ( "struct" ), ( "typeParameter" ), ( "parameter" ),
( "variable" ), ( "property" ), ( "enumMember" ), ( "event" ),
( "function" ), ( "method" ), ( "macro" ), ( "keyword" ),
( "modifier" ), ( "comment" ), ( "string" ), ( "number" ),
( "regexp" ), ( "operator" ) };
}
void LSPClientServer::initialize() {
json codeAction{ { ( "codeActionLiteralSupport" ),
json{ { ( "codeActionKind" ), json{ { ( "valueSet" ), {} } } } } } };
json semanticTokens{
{ ( "requests" ),
json{ { ( "range" ), true }, { ( "full" ), json{ { ( "delta" ), true } } } } },
{ ( "tokenTypes" ), supportedSemanticTokenTypes() },
{ ( "tokenModifiers" ), {} },
{ ( "formats" ), { ( "relative" ) } },
};
json capabilities{
{
( "textDocument" ),
json{
{ ( "documentSymbol" ), json{ { ( "hierarchicalDocumentSymbolSupport" ), true } } },
{ ( "publishDiagnostics" ), json{ { ( "relatedInformation" ), true } } },
{ ( "codeAction" ), codeAction },
{ ( "semanticTokens" ), semanticTokens },
{ ( "synchronization" ), json{ { ( "didSave" ), true } } },
{ ( "selectionRange" ), json{ { ( "dynamicRegistration" ), false } } },
{ ( "hover" ),
json{ { ( "contentFormat" ), { ( "markdown" ), ( "plaintext" ) } } } } },
},
{ ( "window" ), json{ { ( "workDoneProgress" ), true } } } };
json params{ { ( "processId" ), Sys::getProcessID() },
{ ( "rootPath" ), !mRootPath.empty() ? mRootPath : "" },
{ ( "rootUri" ), !mRootPath.empty() ? "file://" + mRootPath : "" },
{ ( "capabilities" ), capabilities },
{ ( "initializationOptions" ), {} } };
write(
newRequest( ( "initialize" ), params ),
[&]( const json& ) {
},
[&]( const json& ) {
} );
}
} // namespace ecode

View File

@@ -0,0 +1,83 @@
#ifndef ECODE_LSPCLIENTSERVER_HPP
#define ECODE_LSPCLIENTSERVER_HPP
#include "lspdefinition.hpp"
#include "lspdocumentclient.hpp"
#include <eepp/system/process.hpp>
#include <eepp/ui/doc/textdocument.hpp>
#include <eepp/ui/doc/undostack.hpp>
#include <memory>
#include <nlohmann/json.hpp>
using json = nlohmann::json;
using namespace EE;
using namespace EE::System;
using namespace EE::UI;
using namespace EE::UI::Doc;
namespace ecode {
class LSPClientServer {
public:
template <typename T> using ReplyHandler = std::function<void( const T& )>;
using GenericReplyHandler = ReplyHandler<json>;
class RequestHandle {
friend class LSPClientServer;
LSPClientServer* mServer;
int mId = 0;
public:
RequestHandle& cancel() {
if ( mServer )
mServer->cancel( mId );
return *this;
}
};
LSPClientServer( const LSPDefinition& lsp, const std::string& rootPath );
~LSPClientServer();
bool start();
bool registerDoc( const std::shared_ptr<TextDocument>& doc );
int cancel( int id ) { return id; }
RequestHandle send( const json& msg, const GenericReplyHandler& h = nullptr,
const GenericReplyHandler& eh = nullptr );
LSPClientServer::RequestHandle didOpen( const URI& document, const std::string& text,
int version );
const LSPDefinition& getDefinition() const { return mLSP; }
LSPClientServer::RequestHandle documentSymbols( const URI& document,
const GenericReplyHandler& h,
const GenericReplyHandler& eh );
protected:
LSPDefinition mLSP;
std::string mRootPath;
Process mProcess;
std::vector<std::shared_ptr<TextDocument>> mDocs;
std::map<TextDocument*, std::unique_ptr<LSPDocumentClient>> mClients;
std::map<int, std::pair<GenericReplyHandler, GenericReplyHandler>> mHandlers;
int mLastMsgId{ 0 };
void readStdOut( const char* bytes, size_t n );
void readStdErr( const char* bytes, size_t n );
LSPClientServer::RequestHandle write( const json& msg, const GenericReplyHandler& h = nullptr,
const GenericReplyHandler& eh = nullptr,
const int id = 0 );
void initialize();
};
} // namespace ecode
#endif // ECODE_LSPCLIENTSERVER_HPP

View File

@@ -0,0 +1,101 @@
#include "lspclientservermanager.hpp"
#include <algorithm>
#include <eepp/system/filesystem.hpp>
#include <eepp/system/luapattern.hpp>
namespace ecode {
LSPClientServerManager::LSPClientServerManager() {}
void LSPClientServerManager::load( const PluginManager* pluginManager,
std::vector<LSPDefinition>&& lsps ) {
mPool = pluginManager->getThreadPool();
mLSPs = lsps;
}
std::vector<LSPDefinition>
LSPClientServerManager::supportsLSP( const std::shared_ptr<TextDocument>& doc ) {
if ( !doc->hasFilepath() && doc->getLoadingFilePath().empty() )
return {};
std::string fileName( FileSystem::fileNameFromPath(
doc->getFilePath().empty() ? doc->getLoadingFilePath() : doc->getFilePath() ) );
const auto& def = doc->getSyntaxDefinition();
std::vector<LSPDefinition> lsps;
for ( auto& lsp : mLSPs ) {
for ( auto& ext : lsp.filePatterns ) {
if ( LuaPattern::find( fileName, ext ).isValid() ) {
lsps.push_back( lsp );
break;
}
auto& files = def.getFiles();
if ( std::find( files.begin(), files.end(), ext ) != files.end() ) {
lsps.push_back( lsp );
break;
}
}
}
return lsps;
}
std::shared_ptr<LSPClientServer>
LSPClientServerManager::runLSPServer( const LSPDefinition& lsp, const std::string& rootPath ) {
auto server = std::make_shared<LSPClientServer>( lsp, rootPath );
server->start();
return server;
}
std::string LSPClientServerManager::findRootPath( const LSPDefinition& lsp,
const std::shared_ptr<TextDocument>& doc ) {
if ( lsp.rootIndicationFileNames.empty() || !doc->hasFilepath() )
return "";
std::string rootPath( doc->getFileInfo().getDirectoryPath() );
std::string lRootPath;
FileSystem::dirAddSlashAtEnd( rootPath );
while ( rootPath != lRootPath ) {
for ( const auto& fileName : lsp.rootIndicationFileNames )
if ( FileSystem::fileExists( rootPath + fileName ) )
return rootPath;
lRootPath = rootPath;
rootPath = FileSystem::removeLastFolderFromPath( rootPath );
}
return "";
}
void LSPClientServerManager::tryRunServer( const std::shared_ptr<TextDocument>& doc ) {
auto lsps = supportsLSP( doc );
if ( lsps.empty() )
return;
for ( const auto& lsp : lsps ) {
auto rootPath = findRootPath( lsp, doc );
auto lspName = lsp.name.empty() ? lsp.command : lsp.name;
String::HashType id = String::hash( lspName + "|" + lsp.language + "|" + rootPath );
auto clientIt = mClients.find( id );
std::shared_ptr<LSPClientServer> server;
if ( clientIt == mClients.end() ) {
server = runLSPServer( lsp, rootPath );
if ( server.use_count() )
mClients[id] = server;
} else {
server = clientIt->second;
}
if ( server.use_count() ) {
server->registerDoc( doc );
}
}
}
void LSPClientServerManager::run( const std::shared_ptr<TextDocument>& doc ) {
mPool->run( [&, doc]() { tryRunServer( doc ); }, []() {} );
}
size_t LSPClientServerManager::clientCount() const {
return mClients.size();
}
} // namespace ecode

View File

@@ -0,0 +1,40 @@
#ifndef ECODE_LSPCLIENTMANAGER_HPP
#define ECODE_LSPCLIENTMANAGER_HPP
#include "../pluginmanager.hpp"
#include "lspclientserver.hpp"
#include "lspdefinition.hpp"
#include <eepp/core.hpp>
using namespace EE;
namespace ecode {
class LSPClientServerManager {
public:
LSPClientServerManager();
void load( const PluginManager* pluginManager, std::vector<LSPDefinition>&& lsps );
void run( const std::shared_ptr<TextDocument>& doc );
size_t clientCount() const;
protected:
std::shared_ptr<ThreadPool> mPool;
std::map<String::HashType, std::shared_ptr<LSPClientServer>> mClients;
std::vector<LSPDefinition> mLSPs;
std::vector<LSPDefinition> supportsLSP( const std::shared_ptr<TextDocument>& doc );
std::shared_ptr<LSPClientServer> runLSPServer( const LSPDefinition& lsp,
const std::string& rootPath );
std::string findRootPath( const LSPDefinition& lsp, const std::shared_ptr<TextDocument>& doc );
void tryRunServer( const std::shared_ptr<TextDocument>& doc );
};
} // namespace ecode
#endif // ECODE_LSPCLIENTMANAGER_HPP

View File

@@ -0,0 +1,19 @@
#ifndef ECODE_LSPDEFINITION_HPP
#define ECODE_LSPDEFINITION_HPP
#include <string>
#include <vector>
namespace ecode {
struct LSPDefinition {
std::string language;
std::string name;
std::vector<std::string> filePatterns;
std::string command;
std::vector<std::string> rootIndicationFileNames;
std::string url;
};
} // namespace ecode
#endif // ECODE_LSPDEFINITION_HPP

View File

@@ -0,0 +1,56 @@
#include "lspdocumentclient.hpp"
#include "lspclientserver.hpp"
#include <eepp/system/filesystem.hpp>
#include <eepp/system/iostreamstring.hpp>
using namespace EE::System;
namespace ecode {
LSPDocumentClient::LSPDocumentClient( LSPClientServer* server, TextDocument* doc ) :
mServer( server ), mDoc( doc ) {
notifyOpen();
mServer->documentSymbols(
mDoc->getURI(),
[&]( const json& ) {
},
[&]( const json& ) {
} );
}
void LSPDocumentClient::onDocumentTextChanged() {}
void LSPDocumentClient::onDocumentUndoRedo( const TextDocument::UndoRedo& /*eventType*/ ) {}
void LSPDocumentClient::onDocumentCursorChange( const TextPosition& ) {}
void LSPDocumentClient::onDocumentSelectionChange( const TextRange& ) {}
void LSPDocumentClient::onDocumentLineCountChange( const size_t& /*lastCount*/,
const size_t& /*newCount*/ ) {}
void LSPDocumentClient::onDocumentLineChanged( const Int64& /*lineIndex*/ ) {}
void LSPDocumentClient::onDocumentSaved( TextDocument* ) {}
void LSPDocumentClient::onDocumentClosed( TextDocument* ) {}
void LSPDocumentClient::onDocumentDirtyOnFileSystem( TextDocument* ) {}
void LSPDocumentClient::onDocumentMoved( TextDocument* ) {}
void LSPDocumentClient::notifyOpen() {
if ( mDoc->isDirty() ) {
IOStreamString text;
mDoc->save( text, true );
mServer->didOpen( mDoc->getURI(), text.getStream(), mVersion );
} else {
std::string text;
FileSystem::fileGet( mDoc->getFilePath(), text );
mServer->didOpen( mDoc->getURI(), text, mVersion );
}
}
} // namespace ecode

View File

@@ -0,0 +1,37 @@
#ifndef ECODE_LSPDOCUMENTCLIENT_HPP
#define ECODE_LSPDOCUMENTCLIENT_HPP
#include <eepp/ui/doc/textdocument.hpp>
using namespace EE;
using namespace EE::UI::Doc;
namespace ecode {
class LSPClientServer;
class LSPDocumentClient : public TextDocument::Client {
public:
LSPDocumentClient( LSPClientServer* server, TextDocument* doc );
virtual void onDocumentTextChanged();
virtual void onDocumentUndoRedo( const TextDocument::UndoRedo& eventType );
virtual void onDocumentCursorChange( const TextPosition& );
virtual void onDocumentSelectionChange( const TextRange& );
virtual void onDocumentLineCountChange( const size_t& lastCount, const size_t& newCount );
virtual void onDocumentLineChanged( const Int64& lineIndex );
virtual void onDocumentSaved( TextDocument* );
virtual void onDocumentClosed( TextDocument* );
virtual void onDocumentDirtyOnFileSystem( TextDocument* );
virtual void onDocumentMoved( TextDocument* );
void notifyOpen();
protected:
LSPClientServer* mServer{ nullptr };
TextDocument* mDoc{ nullptr };
int mVersion{ 0 };
};
} // namespace ecode
#endif // ECODE_LSPDOCUMENTCLIENT_HPP

View File

@@ -1,123 +0,0 @@
#include "lspplugin.hpp"
#include <eepp/system/filesystem.hpp>
#include <eepp/system/lock.hpp>
#include <eepp/system/luapattern.hpp>
#include <eepp/ui/uiscenenode.hpp>
#include <nlohmann/json.hpp>
using json = nlohmann::json;
namespace ecode {
#if EE_PLATFORM != EE_PLATFORM_EMSCRIPTEN || defined( __EMSCRIPTEN_PTHREADS__ )
#define LSP_THREADED 1
#else
#define LSP_THREADED 0
#endif
UICodeEditorPlugin* LSPPlugin::New( const PluginManager* pluginManager ) {
return eeNew( LSPPlugin, ( pluginManager ) );
}
LSPPlugin::LSPPlugin( const PluginManager* pluginManager ) :
mPool( pluginManager->getThreadPool() ) {
#if LSP_THREADED
mPool->run( [&, pluginManager] { load( pluginManager ); }, [] {} );
#else
load( pluginManager );
#endif
}
void LSPPlugin::load( const PluginManager* pluginManager ) {
std::vector<std::string> paths;
std::string path( pluginManager->getResourcesPath() + "plugins/lsp.json" );
if ( FileSystem::fileExists( path ) )
paths.emplace_back( path );
path = pluginManager->getPluginsPath() + "lsp.json";
if ( FileSystem::fileExists( path ) ||
FileSystem::fileWrite( path, "{\n\"config\":{},\n\"lsp\":[]\n}\n" ) ) {
mConfigPath = path;
paths.emplace_back( path );
}
if ( paths.empty() )
return;
for ( const auto& path : paths ) {
try {
loadLSPConfig( path );
} catch ( json::exception& e ) {
Log::error( "Parsing linter \"%s\" failed:\n%s", path.c_str(), e.what() );
}
}
mReady = mClientManager.clientCount() > 0;
if ( mReady )
fireReadyCbs();
}
void LSPPlugin::loadLSPConfig( const std::string& path ) {
}
LSPPlugin::~LSPPlugin() {
mClosing = true;
Lock l( mDocMutex );
for ( const auto& editor : mEditors ) {
for ( auto listener : editor.second )
editor.first->removeEventListener( listener );
editor.first->unregisterPlugin( this );
}
}
void LSPPlugin::onRegister( UICodeEditor* editor ) {
Lock l( mDocMutex );
std::vector<Uint32> listeners;
listeners.push_back( editor->addEventListener( Event::OnDocumentLoaded, [&]( const Event* ) {
} ) );
listeners.push_back(
editor->addEventListener( Event::OnDocumentClosed, [&]( const Event* event ) {
Lock l( mDocMutex );
const DocEvent* docEvent = static_cast<const DocEvent*>( event );
TextDocument* doc = docEvent->getDoc();
mDocs.erase( doc );
} ) );
listeners.push_back(
editor->addEventListener( Event::OnDocumentChanged, [&, editor]( const Event* ) {
TextDocument* oldDoc = mEditorDocs[editor];
TextDocument* newDoc = editor->getDocumentRef().get();
Lock l( mDocMutex );
mDocs.erase( oldDoc );
mEditorDocs[editor] = newDoc;
} ) );
listeners.push_back(
editor->addEventListener( Event::OnCursorPosChange, [&, editor]( const Event* ) {} ) );
listeners.push_back(
editor->addEventListener( Event::OnDocumentSyntaxDefinitionChange, [&]( const Event* ev ) {
// const DocSyntaxDefEvent* event = static_cast<const DocSyntaxDefEvent*>( ev );
} ) );
mEditors.insert( { editor, listeners } );
mDocs.insert( editor->getDocumentRef().get() );
mEditorDocs[editor] = editor->getDocumentRef().get();
}
void LSPPlugin::onUnregister( UICodeEditor* editor ) {
if ( mClosing )
return;
Lock l( mDocMutex );
TextDocument* doc = mEditorDocs[editor];
auto cbs = mEditors[editor];
for ( auto listener : cbs )
editor->removeEventListener( listener );
mEditors.erase( editor );
mEditorDocs.erase( editor );
for ( auto editor : mEditorDocs )
if ( editor.second == doc )
return;
mDocs.erase( doc );
}
} // namespace ecode