mirror of
https://github.com/SpartanJ/eepp.git
synced 2026-08-18 06:55:48 +03:00
Added Sys::getProcessID. Minor fixes.
ecode: LSP Client WIP. Some keybinding refactor.
This commit is contained in:
@@ -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() );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
#include "lspclient.hpp"
|
||||
|
||||
namespace ecode {
|
||||
|
||||
LSPClient::LSPClient() {}
|
||||
|
||||
} // namespace ecode
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
183
src/tools/ecode/plugins/lsp/lspclientplugin.cpp
Normal file
183
src/tools/ecode/plugins/lsp/lspclientplugin.cpp
Normal 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
|
||||
@@ -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
|
||||
238
src/tools/ecode/plugins/lsp/lspclientserver.cpp
Normal file
238
src/tools/ecode/plugins/lsp/lspclientserver.cpp
Normal 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
|
||||
83
src/tools/ecode/plugins/lsp/lspclientserver.hpp
Normal file
83
src/tools/ecode/plugins/lsp/lspclientserver.hpp
Normal 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
|
||||
101
src/tools/ecode/plugins/lsp/lspclientservermanager.cpp
Normal file
101
src/tools/ecode/plugins/lsp/lspclientservermanager.cpp
Normal 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
|
||||
40
src/tools/ecode/plugins/lsp/lspclientservermanager.hpp
Normal file
40
src/tools/ecode/plugins/lsp/lspclientservermanager.hpp
Normal 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
|
||||
19
src/tools/ecode/plugins/lsp/lspdefinition.hpp
Normal file
19
src/tools/ecode/plugins/lsp/lspdefinition.hpp
Normal 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
|
||||
56
src/tools/ecode/plugins/lsp/lspdocumentclient.cpp
Normal file
56
src/tools/ecode/plugins/lsp/lspdocumentclient.cpp
Normal 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
|
||||
37
src/tools/ecode/plugins/lsp/lspdocumentclient.hpp
Normal file
37
src/tools/ecode/plugins/lsp/lspdocumentclient.hpp
Normal 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
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user