mirror of
https://github.com/SpartanJ/eepp.git
synced 2026-05-31 18:46:29 +03:00
LSP Client advances. Some LSPs are responding fine.
This commit is contained in:
@@ -1169,6 +1169,7 @@
|
||||
../../src/tools/ecode/plugins/linter/linterplugin.hpp
|
||||
../../src/tools/ecode/plugins/lsp/lspclientplugin.cpp
|
||||
../../src/tools/ecode/plugins/lsp/lspclientplugin.hpp
|
||||
../../src/tools/ecode/plugins/lsp/lspclientprotocol.hpp
|
||||
../../src/tools/ecode/plugins/lsp/lspclientserver.cpp
|
||||
../../src/tools/ecode/plugins/lsp/lspclientserver.hpp
|
||||
../../src/tools/ecode/plugins/lsp/lspclientservermanager.cpp
|
||||
|
||||
@@ -129,7 +129,9 @@ size_t Process::write( const char* buffer, const size_t& size ) {
|
||||
eeASSERT( mProcess != nullptr );
|
||||
Lock l( mStdInMutex );
|
||||
FILE* stdInFile = subprocess_stdin( PROCESS_PTR );
|
||||
return fwrite( buffer, sizeof( char ), size, stdInFile );
|
||||
int ret = fwrite( buffer, 1, size, stdInFile );
|
||||
fflush( stdInFile );
|
||||
return ret;
|
||||
}
|
||||
|
||||
size_t Process::write( const std::string& buffer ) {
|
||||
@@ -201,7 +203,7 @@ void Process::startAsyncRead( ReadFn readStdOut, ReadFn readStdErr ) {
|
||||
}
|
||||
} );
|
||||
}
|
||||
if ( stdErrFd ) {
|
||||
if ( stdErrFd && stdErrFd != stdOutFd ) {
|
||||
mStdErrThread = std::thread( [this, stdErrFd]() {
|
||||
DWORD n;
|
||||
std::string buffer;
|
||||
@@ -231,7 +233,7 @@ void Process::startAsyncRead( ReadFn readStdOut, ReadFn readStdErr ) {
|
||||
: -1;
|
||||
pollfds.back().events = POLLIN;
|
||||
}
|
||||
if ( stdErrFd ) {
|
||||
if ( stdErrFd && stdOutFd != stdErrFd ) {
|
||||
pollfds.emplace_back();
|
||||
pollfds.back().fd =
|
||||
fcntl( stdErrFd, F_SETFL, fcntl( stdErrFd, F_GETFL ) | O_NONBLOCK ) == 0 ? stdErrFd
|
||||
|
||||
@@ -159,6 +159,9 @@ UICodeEditor::UICodeEditor( const bool& autoRegisterBaseCommands,
|
||||
UICodeEditor( "codeeditor", autoRegisterBaseCommands, autoRegisterBaseKeybindings ) {}
|
||||
|
||||
UICodeEditor::~UICodeEditor() {
|
||||
for ( auto& plugin : mPlugins )
|
||||
plugin->onUnregister( this );
|
||||
|
||||
if ( mDoc.use_count() == 1 ) {
|
||||
DocEvent event( this, mDoc.get(), Event::OnDocumentClosed );
|
||||
sendEvent( &event );
|
||||
@@ -167,8 +170,6 @@ UICodeEditor::~UICodeEditor() {
|
||||
} else {
|
||||
mDoc->unregisterClient( this );
|
||||
}
|
||||
for ( auto& plugin : mPlugins )
|
||||
plugin->onUnregister( this );
|
||||
}
|
||||
|
||||
Uint32 UICodeEditor::getType() const {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#include "plugins/autocomplete/autocompleteplugin.hpp"
|
||||
#include "plugins/formatter/formatterplugin.hpp"
|
||||
#include "plugins/linter/linterplugin.hpp"
|
||||
// #include "plugins/lsp/lspclientplugin.hpp"
|
||||
#include "plugins/lsp/lspclientplugin.hpp"
|
||||
#include "version.hpp"
|
||||
#include <algorithm>
|
||||
#include <args/args.hxx>
|
||||
@@ -369,7 +369,7 @@ void App::initPluginManager() {
|
||||
mPluginManager->registerPlugin( LinterPlugin::Definition() );
|
||||
mPluginManager->registerPlugin( FormatterPlugin::Definition() );
|
||||
mPluginManager->registerPlugin( AutoCompletePlugin::Definition() );
|
||||
// mPluginManager->registerPlugin( LSPClientPlugin::Definition() );
|
||||
mPluginManager->registerPlugin( LSPClientPlugin::Definition() );
|
||||
}
|
||||
|
||||
void App::loadConfig( const LogLevel& logLevel ) {
|
||||
@@ -3355,6 +3355,8 @@ void App::loadFolder( const std::string& path ) {
|
||||
|
||||
std::string rpath( FileSystem::getRealPath( path ) );
|
||||
mCurrentProject = rpath;
|
||||
mPluginManager->setWorkspaceFolder( rpath );
|
||||
|
||||
loadDirTree( rpath );
|
||||
|
||||
mConfig.loadProject( rpath, mSplitter, mConfigPath, mProjectDocConfig, mThreadPool, this );
|
||||
|
||||
@@ -35,7 +35,21 @@ void LSPClientPlugin::update( UICodeEditor* ) {
|
||||
mClientManager.updateDirty();
|
||||
}
|
||||
|
||||
void LSPClientPlugin::processNotification( PluginManager::Notification noti,
|
||||
const nlohmann::json& obj ) {
|
||||
switch ( noti ) {
|
||||
case PluginManager::WorkspaceFolderChanged: {
|
||||
mClientManager.didChangeWorkspaceFolders( obj["folder"] );
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void LSPClientPlugin::load( const PluginManager* pluginManager ) {
|
||||
pluginManager->subscribeNotifications(
|
||||
this, [&]( auto noti, auto obj ) { processNotification( noti, obj ); } );
|
||||
std::vector<std::string> paths;
|
||||
std::string path( pluginManager->getResourcesPath() + "plugins/lspclient.json" );
|
||||
if ( FileSystem::fileExists( path ) )
|
||||
@@ -107,6 +121,8 @@ void LSPClientPlugin::loadLSPConfig( std::vector<LSPDefinition>& lsps, const std
|
||||
foundTlsp = true;
|
||||
lsp.command = tlsp.command;
|
||||
lsp.name = tlsp.name;
|
||||
lsp.rootIndicationFileNames = tlsp.rootIndicationFileNames;
|
||||
lsp.url = tlsp.url;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -130,6 +146,7 @@ void LSPClientPlugin::loadLSPConfig( std::vector<LSPDefinition>& lsps, const std
|
||||
lsp.filePatterns.push_back( pattern.get<std::string>() );
|
||||
|
||||
if ( obj.contains( "rootIndicationFileNames" ) ) {
|
||||
lsp.rootIndicationFileNames.clear();
|
||||
auto fnms = obj["rootIndicationFileNames"];
|
||||
for ( auto& fn : fnms )
|
||||
lsp.rootIndicationFileNames.push_back( fn );
|
||||
|
||||
@@ -73,6 +73,8 @@ 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& );
|
||||
};
|
||||
|
||||
} // namespace ecode
|
||||
|
||||
112
src/tools/ecode/plugins/lsp/lspclientprotocol.hpp
Normal file
112
src/tools/ecode/plugins/lsp/lspclientprotocol.hpp
Normal file
@@ -0,0 +1,112 @@
|
||||
#ifndef ECODE_LSPCLIENTPROTOCOL_HPP
|
||||
#define ECODE_LSPCLIENTPROTOCOL_HPP
|
||||
|
||||
#include <eepp/ui/doc/textdocument.hpp>
|
||||
#include <string>
|
||||
|
||||
using namespace EE::UI::Doc;
|
||||
using namespace EE::Network;
|
||||
|
||||
namespace ecode {
|
||||
|
||||
enum class LSPErrorCode {
|
||||
// Defined by JSON RPC
|
||||
ParseError = -32700,
|
||||
InvalidRequest = -32600,
|
||||
MethodNotFound = -32601,
|
||||
InvalidParams = -32602,
|
||||
InternalError = -32603,
|
||||
serverErrorStart = -32099,
|
||||
serverErrorEnd = -32000,
|
||||
ServerNotInitialized = -32002,
|
||||
UnknownErrorCode = -32001,
|
||||
|
||||
// Defined by the protocol.
|
||||
RequestCancelled = -32800,
|
||||
ContentModified = -32801
|
||||
};
|
||||
|
||||
struct LSPLocation {
|
||||
URI uri;
|
||||
TextRange range;
|
||||
};
|
||||
|
||||
struct LSPWorkspaceFolder {
|
||||
URI uri;
|
||||
std::string name;
|
||||
};
|
||||
|
||||
enum class LSPDocumentSyncKind { None = 0, Full = 1, Incremental = 2 };
|
||||
|
||||
struct LSPSaveOptions {
|
||||
bool includeText = false;
|
||||
};
|
||||
|
||||
struct LSPTextDocumentSyncOptions {
|
||||
LSPDocumentSyncKind change = LSPDocumentSyncKind::None;
|
||||
LSPSaveOptions save;
|
||||
};
|
||||
|
||||
struct LSPCompletionOptions {
|
||||
bool provider = false;
|
||||
bool resolveProvider = false;
|
||||
std::vector<char> triggerCharacters;
|
||||
};
|
||||
|
||||
struct LSPSignatureHelpOptions {
|
||||
bool provider = false;
|
||||
std::vector<char> triggerCharacters;
|
||||
};
|
||||
|
||||
struct LSPDocumentOnTypeFormattingOptions : public LSPSignatureHelpOptions {};
|
||||
|
||||
// Ref:
|
||||
// https://microsoft.github.io/language-server-protocol/specification#textDocument_semanticTokens
|
||||
struct LSPSemanticTokensOptions {
|
||||
bool full = false;
|
||||
bool fullDelta = false;
|
||||
bool range = false;
|
||||
// SemanticTokensLegend legend;
|
||||
};
|
||||
|
||||
struct LSPWorkspaceFoldersServerCapabilities {
|
||||
bool supported = false;
|
||||
bool changeNotifications = false;
|
||||
};
|
||||
|
||||
struct LSPServerCapabilities {
|
||||
LSPTextDocumentSyncOptions textDocumentSync;
|
||||
bool hoverProvider = false;
|
||||
LSPCompletionOptions completionProvider;
|
||||
LSPSignatureHelpOptions signatureHelpProvider;
|
||||
bool definitionProvider = false;
|
||||
// official extension as of 3.14.0
|
||||
bool declarationProvider = false;
|
||||
bool typeDefinitionProvider = false;
|
||||
bool referencesProvider = false;
|
||||
bool implementationProvider = false;
|
||||
bool documentSymbolProvider = false;
|
||||
bool documentHighlightProvider = false;
|
||||
bool documentFormattingProvider = false;
|
||||
bool documentRangeFormattingProvider = false;
|
||||
LSPDocumentOnTypeFormattingOptions documentOnTypeFormattingProvider;
|
||||
bool renameProvider = false;
|
||||
// CodeActionOptions not useful/considered at present
|
||||
bool codeActionProvider = false;
|
||||
LSPSemanticTokensOptions semanticTokenProvider;
|
||||
// workspace caps flattened
|
||||
// (other parts not useful/considered at present)
|
||||
LSPWorkspaceFoldersServerCapabilities workspaceFolders;
|
||||
bool selectionRangeProvider = false;
|
||||
};
|
||||
|
||||
enum class LSPMessageType { Error = 1, Warning = 2, Info = 3, Log = 4 };
|
||||
|
||||
struct LSPShowMessageParams {
|
||||
LSPMessageType type;
|
||||
std::string message;
|
||||
};
|
||||
|
||||
} // namespace ecode
|
||||
|
||||
#endif // ECODE_LSPCLIENTPROTOCOL_HPP
|
||||
@@ -10,6 +10,9 @@
|
||||
|
||||
namespace ecode {
|
||||
|
||||
#define CONTENT_LENGTH "Content-Length"
|
||||
#define CONTENT_LENGTH_HEADER "Content-Length:"
|
||||
|
||||
static const char* MEMBER_ID = "id";
|
||||
static const char* MEMBER_METHOD = "method";
|
||||
static const char* MEMBER_PARAMS = "params";
|
||||
@@ -18,8 +21,8 @@ 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_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";
|
||||
@@ -44,10 +47,10 @@ 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 ) {
|
||||
static json newRequest( const std::string& method, const json& params = {} ) {
|
||||
json j;
|
||||
j[MEMBER_METHOD] = method;
|
||||
j[MEMBER_PARAMS] = params;
|
||||
j[MEMBER_PARAMS] = params.empty() ? json() : params;
|
||||
return j;
|
||||
}
|
||||
|
||||
@@ -61,8 +64,8 @@ static json versionedTextDocumentIdentifier( const URI& document, int version =
|
||||
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;
|
||||
map[MEMBER_TEXT] = text;
|
||||
return map;
|
||||
}
|
||||
|
||||
@@ -73,33 +76,195 @@ static json textDocumentParams( const json& m ) {
|
||||
static json textDocumentParams( const URI& document, int version = -1 ) {
|
||||
return textDocumentParams( versionedTextDocumentIdentifier( document, version ) );
|
||||
}
|
||||
static json to_json( const TextPosition& pos ) {
|
||||
static json toJson( const TextPosition& pos ) {
|
||||
return json{ { MEMBER_LINE, pos.line() }, { MEMBER_CHARACTER, pos.column() } };
|
||||
}
|
||||
|
||||
static json workspaceFolder( const LSPWorkspaceFolder& response ) {
|
||||
return json{ { MEMBER_URI, response.uri.toString() }, { "name", response.name } };
|
||||
}
|
||||
|
||||
static json toJson( const std::vector<LSPWorkspaceFolder>& l ) {
|
||||
if ( l.empty() )
|
||||
return json::array();
|
||||
json result;
|
||||
for ( const auto& e : l )
|
||||
result.push_back( workspaceFolder( e ) );
|
||||
return result;
|
||||
}
|
||||
|
||||
static TextPosition parsePosition( const json& m ) {
|
||||
auto line = m[MEMBER_LINE].get<int>();
|
||||
auto column = m[MEMBER_CHARACTER].get<int>();
|
||||
return { line, column };
|
||||
}
|
||||
|
||||
static TextRange parseRange( const json& range ) {
|
||||
auto startpos = parsePosition( range[MEMBER_START] );
|
||||
auto endpos = parsePosition( range[MEMBER_END] );
|
||||
return { startpos, endpos };
|
||||
}
|
||||
|
||||
static LSPLocation parseLocation( const json& loc ) {
|
||||
auto uri = URI( loc[MEMBER_URI].get<std::string>() );
|
||||
auto range = parseRange( loc[MEMBER_RANGE] );
|
||||
return { uri, range };
|
||||
}
|
||||
|
||||
static LSPLocation parseLocationLink( const json& loc ) {
|
||||
auto uri = URI( loc[MEMBER_TARGET_URI].get<std::string>() );
|
||||
auto vrange = loc[MEMBER_TARGET_SELECTION_RANGE];
|
||||
if ( vrange.is_null() )
|
||||
vrange = loc[MEMBER_TARGET_RANGE];
|
||||
auto range = parseRange( vrange );
|
||||
return { uri, range };
|
||||
}
|
||||
|
||||
static std::vector<LSPLocation> parseDocumentLocation( const json& result ) {
|
||||
std::vector<LSPLocation> ret;
|
||||
if ( result.is_array() ) {
|
||||
const auto& locs = result;
|
||||
for ( const auto& def : locs ) {
|
||||
const auto& ob = def;
|
||||
ret.push_back( parseLocation( ob ) );
|
||||
if ( ret.back().uri.empty() )
|
||||
ret.back() = parseLocationLink( ob );
|
||||
}
|
||||
} else if ( result.is_object() ) {
|
||||
ret.push_back( parseLocation( result ) );
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static json changeWorkspaceFoldersParams( const std::vector<LSPWorkspaceFolder>& added,
|
||||
const std::vector<LSPWorkspaceFolder>& removed ) {
|
||||
json event;
|
||||
event["added"] = toJson( added );
|
||||
event["removed"] = toJson( removed );
|
||||
return json{ { "event", event } };
|
||||
}
|
||||
|
||||
static json textDocumentPositionParams( const URI& document, TextPosition pos ) {
|
||||
auto params = textDocumentParams( document );
|
||||
params[MEMBER_POSITION] = to_json( pos );
|
||||
params[MEMBER_POSITION] = toJson( pos );
|
||||
return params;
|
||||
}
|
||||
|
||||
static void fromJson( std::vector<char>& trigger, const json& json ) {
|
||||
if ( !json.empty() ) {
|
||||
const auto triggersArray = json;
|
||||
for ( const auto& t : triggersArray ) {
|
||||
auto st = t.get<std::string>();
|
||||
if ( st.length() )
|
||||
trigger.push_back( st.at( 0 ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void fromJson( LSPCompletionOptions& options, const json& json ) {
|
||||
if ( !json.empty() && json.is_object() ) {
|
||||
auto ob = json;
|
||||
options.provider = true;
|
||||
options.resolveProvider = ob["resolveProvider"].get<bool>();
|
||||
fromJson( options.triggerCharacters, ob["triggerCharacters"] );
|
||||
}
|
||||
}
|
||||
|
||||
static void fromJson( LSPSignatureHelpOptions& options, const json& json ) {
|
||||
if ( !json.empty() && json.is_object() ) {
|
||||
auto ob = json;
|
||||
options.provider = true;
|
||||
fromJson( options.triggerCharacters, ob["triggerCharacters"] );
|
||||
}
|
||||
}
|
||||
|
||||
static void fromJson( LSPDocumentOnTypeFormattingOptions& options, const json& json ) {
|
||||
if ( !json.empty() && json.is_object() ) {
|
||||
auto ob = json;
|
||||
options.provider = true;
|
||||
fromJson( options.triggerCharacters, ob["moreTriggerCharacter"] );
|
||||
auto trigger = ob["firstTriggerCharacter"].get<std::string>();
|
||||
if ( trigger.size() )
|
||||
options.triggerCharacters.push_back( trigger.at( 0 ) );
|
||||
}
|
||||
}
|
||||
|
||||
static void fromJson( LSPWorkspaceFoldersServerCapabilities& options, const json& json ) {
|
||||
if ( json.is_object() ) {
|
||||
auto ob = json;
|
||||
options.supported = ob["supported"].get<bool>();
|
||||
auto notify = ob["changeNotifications"].get<bool>();
|
||||
options.changeNotifications = notify;
|
||||
}
|
||||
}
|
||||
|
||||
static void fromJson( LSPServerCapabilities& caps, const json& json ) {
|
||||
// in older protocol versions a support option is simply a boolean
|
||||
// in newer version it may be an object instead;
|
||||
// it should not be sent unless such support is announced, but let's handle it anyway
|
||||
// so consider an object there as a (good?) sign that the server is suitably capable
|
||||
auto toBoolOrObject = []( const nlohmann::json& value, const std::string& valueName ) {
|
||||
return value.contains( "valueName" ) &&
|
||||
( value[valueName].get<bool>() || value[valueName].is_object() );
|
||||
};
|
||||
|
||||
auto sync = json["textDocumentSync"];
|
||||
caps.textDocumentSync.change = static_cast<LSPDocumentSyncKind>(
|
||||
( sync.is_object() ? sync["change"].get<int>() : sync.get<bool>() ) );
|
||||
if ( sync.is_object() ) {
|
||||
auto syncObject = sync;
|
||||
auto save = syncObject["save"];
|
||||
if ( !save.empty() && ( save.is_object() || save.get<bool>() ) ) {
|
||||
caps.textDocumentSync.save = { save["includeText"].get<bool>() };
|
||||
}
|
||||
}
|
||||
|
||||
caps.hoverProvider = toBoolOrObject( json, "hoverProvider" );
|
||||
fromJson( caps.completionProvider, json["completionProvider"] );
|
||||
fromJson( caps.signatureHelpProvider, json["signatureHelpProvider"] );
|
||||
caps.definitionProvider = toBoolOrObject( json, "definitionProvider" );
|
||||
caps.declarationProvider = toBoolOrObject( json, "declarationProvider" );
|
||||
caps.typeDefinitionProvider = toBoolOrObject( json, "typeDefinitionProvider" );
|
||||
caps.referencesProvider = toBoolOrObject( json, "referencesProvider" );
|
||||
caps.implementationProvider = toBoolOrObject( json, "implementationProvider" );
|
||||
caps.documentSymbolProvider = toBoolOrObject( json, "documentSymbolProvider" );
|
||||
caps.documentHighlightProvider = toBoolOrObject( json, "documentHighlightProvider" );
|
||||
caps.documentFormattingProvider = toBoolOrObject( json, "documentFormattingProvider" );
|
||||
caps.documentRangeFormattingProvider =
|
||||
toBoolOrObject( json, "documentRangeFormattingProvider" );
|
||||
fromJson( caps.documentOnTypeFormattingProvider, json["documentOnTypeFormattingProvider"] );
|
||||
caps.renameProvider = toBoolOrObject( json, "renameProvider" );
|
||||
if ( json.contains( "codeActionProvider" ) &&
|
||||
json["codeActionProvider"].contains( "resolveProvider" ) ) {
|
||||
auto codeActionProvider = json["codeActionProvider"];
|
||||
caps.codeActionProvider = json["codeActionProvider"]["resolveProvider"].get<bool>();
|
||||
}
|
||||
// fromJson( caps.semanticTokenProvider, json["semanticTokensProvider"] );
|
||||
if ( json.contains( "workspace" ) ) {
|
||||
auto workspace = json["workspace"];
|
||||
fromJson( caps.workspaceFolders, workspace["workspaceFolders"] );
|
||||
}
|
||||
caps.selectionRangeProvider = toBoolOrObject( json, "selectionRangeProvider" );
|
||||
}
|
||||
|
||||
LSPClientServer::LSPClientServer( LSPClientServerManager* manager, const String::HashType& id,
|
||||
const LSPDefinition& lsp, const std::string& rootPath ) :
|
||||
mManager( manager ), mId( id ), mLSP( lsp ), mRootPath( rootPath ) {}
|
||||
|
||||
LSPClientServer::~LSPClientServer() {
|
||||
Lock l( mClientsMutex );
|
||||
for ( const auto& client : mClients )
|
||||
client.first->unregisterClient( client.second.get() );
|
||||
{
|
||||
Lock l( mClientsMutex );
|
||||
for ( const auto& client : mClients )
|
||||
client.first->unregisterClient( client.second.get() );
|
||||
}
|
||||
}
|
||||
|
||||
bool LSPClientServer::start() {
|
||||
bool ret = mProcess.create(
|
||||
mLSP.command, Process::getDefaultOptions() | (Uint32)Process::Options::CombinedStdoutStderr,
|
||||
{}, mRootPath );
|
||||
bool ret = mProcess.create( mLSP.command, Process::getDefaultOptions(), {}, mRootPath );
|
||||
if ( ret ) {
|
||||
mProcess.startAsyncRead( [this]( const char* bytes, size_t n ) { readStdOut( bytes, n ); },
|
||||
[]( const char*, size_t ) {} );
|
||||
mProcess.startAsyncRead(
|
||||
[this]( const char* bytes, size_t n ) { readStdOut( bytes, n ); },
|
||||
[this]( const char* bytes, size_t n ) { readStdErr( bytes, n ); } );
|
||||
|
||||
initialize();
|
||||
}
|
||||
@@ -157,10 +322,18 @@ LSPClientServer::RequestHandle LSPClientServer::write( const json& msg,
|
||||
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 );
|
||||
if ( mReady || msg[MEMBER_METHOD] == "initialize" ) {
|
||||
std::string method;
|
||||
if ( msg.contains( MEMBER_METHOD ) )
|
||||
method = msg[MEMBER_METHOD].get<std::string>();
|
||||
else if ( msg.contains( MEMBER_MESSAGE ) )
|
||||
method = msg[MEMBER_MESSAGE];
|
||||
Log::info( "LSPClientServer calling %s", method.c_str() );
|
||||
Log::debug( "LSPClientServer sending message:\n%s", sjson.c_str() );
|
||||
mProcess.write( sjson );
|
||||
} else {
|
||||
mQueuedMessages.push_back( { std::move( ob ), h, eh } );
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
@@ -238,6 +411,10 @@ bool LSPClientServer::hasDocument( TextDocument* doc ) const {
|
||||
return std::find( mDocs.begin(), mDocs.end(), doc ) != mDocs.end();
|
||||
}
|
||||
|
||||
bool LSPClientServer::hasDocuments() const {
|
||||
return !mDocs.empty();
|
||||
}
|
||||
|
||||
LSPClientServer::RequestHandle LSPClientServer::didClose( const URI& document ) {
|
||||
auto params = textDocumentParams( document );
|
||||
return send( newRequest( "textDocument/didClose", params ) );
|
||||
@@ -250,9 +427,6 @@ LSPClientServer::RequestHandle LSPClientServer::didClose( TextDocument* doc ) {
|
||||
auto it = std::find( mDocs.begin(), mDocs.end(), doc );
|
||||
if ( it != mDocs.end() )
|
||||
mDocs.erase( it );
|
||||
// No more docs are being used, close the LSP
|
||||
if ( mDocs.empty() )
|
||||
mManager->notifyClose( mId );
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
@@ -272,7 +446,59 @@ LSPClientServer::RequestHandle LSPClientServer::documentSymbols( const URI& docu
|
||||
return send( newRequest( "textDocument/documentSymbol", params ), h, eh );
|
||||
}
|
||||
|
||||
/*enum class LSPWorkDoneProgressKind { Begin, Report, End };
|
||||
|
||||
struct LSPWorkDoneProgressValue {
|
||||
LSPWorkDoneProgressKind kind;
|
||||
std::string title;
|
||||
std::string message;
|
||||
bool cancellable;
|
||||
unsigned percentage;
|
||||
};
|
||||
template <typename T> struct LSPProgressParams {
|
||||
// number or string
|
||||
json token;
|
||||
T value;
|
||||
};
|
||||
|
||||
using LSPWorkDoneProgressParams = LSPProgressParams<LSPWorkDoneProgressValue>;
|
||||
|
||||
void fromJson( LSPWorkDoneProgressValue& value, const json& json ) {
|
||||
if ( json ) {
|
||||
auto ob = json;
|
||||
auto kind = ob["kind"].get<std::string>();
|
||||
if ( kind == "begin" ) {
|
||||
value.kind = LSPWorkDoneProgressKind::Begin;
|
||||
} else if ( kind == "report" ) {
|
||||
value.kind = LSPWorkDoneProgressKind::Report;
|
||||
} else if ( kind == "end" ) {
|
||||
value.kind = LSPWorkDoneProgressKind::End;
|
||||
}
|
||||
value.title = ob["title"].get<std::string>();
|
||||
value.message = ob["message"].get<std::string>();
|
||||
value.cancellable = ob["cancellable"].get<bool>();
|
||||
value.percentage = ob["percentage"].get<int>();
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T> static LSPProgressParams<T> parseProgress( const json& json ) {
|
||||
LSPProgressParams<T> ret;
|
||||
ret.token = json["token"];
|
||||
fromJson( ret.value, json["value"] );
|
||||
return ret;
|
||||
}
|
||||
|
||||
static LSPWorkDoneProgressParams parseWorkDone( const json& json ) {
|
||||
return parseProgress<LSPWorkDoneProgressValue>( json );
|
||||
}*/
|
||||
|
||||
static json newError( const LSPErrorCode& code, const std::string& msg ) {
|
||||
return json{
|
||||
{ MEMBER_ERROR, { { MEMBER_CODE, static_cast<int>( code ) }, { MEMBER_MESSAGE, msg } } } };
|
||||
}
|
||||
|
||||
void LSPClientServer::processNotification( const json& msg ) {
|
||||
Log::warning( "LSPClientServer::processNotification: %s", msg.dump().c_str() );
|
||||
auto method = msg[MEMBER_METHOD].get<std::string>();
|
||||
if ( method == "textDocument/publishDiagnostics" ) {
|
||||
// publishDiagnostics( msg );
|
||||
@@ -281,61 +507,120 @@ void LSPClientServer::processNotification( const json& msg ) {
|
||||
} else if ( method == ( "window/logMessage" ) ) {
|
||||
// logMessage( msg );
|
||||
} else if ( method == ( "$/progress" ) ) {
|
||||
// workDoneProgress( msg );
|
||||
// workDoneProgress( parseWorkDone( msg["params"] ) );
|
||||
} else {
|
||||
Log::warning( "LSPClientServer::processNotification msg discarded: %s",
|
||||
msg.dump( 2, ' ' ) );
|
||||
}
|
||||
}
|
||||
void LSPClientServer::processRequest( const json& /*msg*/ ) {
|
||||
// auto method = msg[MEMBER_METHOD].get<std::string>();
|
||||
// auto msgid = msg[MEMBER_ID];
|
||||
|
||||
void LSPClientServer::processRequest( const json& msg ) {
|
||||
Log::debug( "LSPClientServer::processRequest: %s", msg.dump().c_str() );
|
||||
auto method = msg[MEMBER_METHOD].get<std::string>();
|
||||
auto msgid = msg[MEMBER_ID].get<int>();
|
||||
// auto params = msg[MEMBER_PARAMS];
|
||||
// bool handled = false;
|
||||
write( newError( LSPErrorCode::MethodNotFound, method ), nullptr, nullptr, msgid );
|
||||
}
|
||||
|
||||
void LSPClientServer::readStdOut( const char* bytes, size_t /*n*/ ) {
|
||||
const char* skipLength = strstr( bytes, "\r\n\r\n" );
|
||||
if ( nullptr != skipLength ) {
|
||||
try {
|
||||
auto res = json::parse( skipLength + 4 );
|
||||
Log::debug( "LSP Server %s said: \n%s", mLSP.name.c_str(), res.dump( 2, ' ' ).c_str() );
|
||||
void LSPClientServer::readStdOut( const char* bytes, size_t n ) {
|
||||
mReceive.append( bytes, n );
|
||||
|
||||
int msgid = -1;
|
||||
if ( res.contains( MEMBER_ID ) ) {
|
||||
msgid = res[MEMBER_ID].get<int>();
|
||||
} else {
|
||||
processNotification( res );
|
||||
return;
|
||||
}
|
||||
std::string& buffer = mReceive;
|
||||
|
||||
if ( res.contains( MEMBER_METHOD ) ) {
|
||||
processRequest( res );
|
||||
return;
|
||||
}
|
||||
|
||||
auto it = mHandlers.find( msgid );
|
||||
if ( it != mHandlers.end() ) {
|
||||
const auto handler = *it;
|
||||
mHandlers.erase( it );
|
||||
auto& h = handler.second.first;
|
||||
auto& eh = handler.second.second;
|
||||
if ( res.contains( MEMBER_ERROR ) && eh ) {
|
||||
eh( res[MEMBER_ERROR] );
|
||||
} else {
|
||||
h( res[MEMBER_RESULT] );
|
||||
}
|
||||
} else {
|
||||
Log::debug( "LSPClientServer::readStdOut unexpected reply id: %d", msgid );
|
||||
}
|
||||
|
||||
return;
|
||||
} catch ( const json::exception& e ) {
|
||||
Log::debug( "LSP Server %s said: Coudln't parse json err: %s", mLSP.name.c_str(),
|
||||
e.what() );
|
||||
while ( true ) {
|
||||
auto index = buffer.find_first_of( CONTENT_LENGTH_HEADER );
|
||||
if ( index == std::string::npos ) {
|
||||
if ( buffer.size() > ( 1 << 20 ) )
|
||||
buffer.clear();
|
||||
break;
|
||||
}
|
||||
|
||||
index += strlen( CONTENT_LENGTH_HEADER );
|
||||
auto endindex = buffer.find_first_of( "\r\n", index );
|
||||
auto msgstart = buffer.find_first_of( "\r\n\r\n", index );
|
||||
if ( endindex == std::string::npos || msgstart == std::string::npos )
|
||||
break;
|
||||
|
||||
msgstart += 4;
|
||||
int length = 0;
|
||||
bool ok = String::fromString( length, buffer.substr( index, endindex - index ) );
|
||||
// FIXME perhaps detect if no reply for some time
|
||||
// then again possibly better left to user to restart in such case
|
||||
if ( !ok ) {
|
||||
Log::error( "LSPClientServer::readStdOut invalid " CONTENT_LENGTH );
|
||||
// flush and try to carry on to some next header
|
||||
buffer.erase( 0, msgstart );
|
||||
continue;
|
||||
}
|
||||
// sanity check to avoid extensive buffering
|
||||
if ( length > ( 1 << 29 ) ) {
|
||||
Log::error( "LSPClientServer::readStdOut excessive size" );
|
||||
buffer.clear();
|
||||
continue;
|
||||
}
|
||||
if ( msgstart + length > buffer.length() ) {
|
||||
break;
|
||||
}
|
||||
|
||||
// now onto payload
|
||||
auto payload = buffer.substr( msgstart, length );
|
||||
buffer.erase( 0, msgstart + length );
|
||||
|
||||
if ( !payload.empty() ) {
|
||||
try {
|
||||
auto res = json::parse( payload );
|
||||
|
||||
int msgid = -1;
|
||||
if ( res.contains( MEMBER_ID ) ) {
|
||||
msgid = res[MEMBER_ID].get<int>();
|
||||
} else {
|
||||
processNotification( res );
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( res.contains( MEMBER_METHOD ) ) {
|
||||
processRequest( res );
|
||||
continue;
|
||||
}
|
||||
|
||||
Log::debug( "LSP Server %s said: \n%s", mLSP.name.c_str(), res.dump().c_str() );
|
||||
|
||||
auto it = mHandlers.find( msgid );
|
||||
if ( it != mHandlers.end() ) {
|
||||
const auto handler = *it;
|
||||
mHandlers.erase( it );
|
||||
auto& h = handler.second.first;
|
||||
auto& eh = handler.second.second;
|
||||
if ( res.contains( MEMBER_ERROR ) && eh ) {
|
||||
eh( res[MEMBER_ERROR] );
|
||||
} else {
|
||||
h( res[MEMBER_RESULT] );
|
||||
}
|
||||
} else {
|
||||
Log::debug( "LSPClientServer::readStdOut unexpected reply id: %d", msgid );
|
||||
}
|
||||
|
||||
continue;
|
||||
} 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(), payload.c_str() );
|
||||
}
|
||||
}
|
||||
|
||||
void LSPClientServer::readStdErr( const char* bytes, size_t n ) {
|
||||
mReceiveErr += std::string( bytes, n );
|
||||
LSPShowMessageParams msg;
|
||||
const auto lastNewLineIndex = mReceiveErr.find_last_of( '\n' );
|
||||
if ( lastNewLineIndex != std::string::npos ) {
|
||||
msg.message = mReceiveErr.substr( 0, lastNewLineIndex );
|
||||
mReceiveErr.erase( 0, lastNewLineIndex + 1 );
|
||||
}
|
||||
if ( !msg.message.empty() ) {
|
||||
msg.type = LSPMessageType::Log;
|
||||
Log::warning( "LSPClientServer::readStdErr: %s", msg.message.c_str() );
|
||||
}
|
||||
Log::debug( "LSP Server %s said: \n%s", mLSP.name.c_str(), bytes );
|
||||
}
|
||||
|
||||
static std::vector<std::string> supportedSemanticTokenTypes() {
|
||||
@@ -370,62 +655,44 @@ void LSPClientServer::initialize() {
|
||||
{ "window", json{ { "workDoneProgress", true } } } };
|
||||
|
||||
json params{ { "processId", Sys::getProcessID() },
|
||||
{ "rootPath", !mRootPath.empty() ? mRootPath : "" },
|
||||
{ "rootUri", !mRootPath.empty() ? "file://" + mRootPath : "" },
|
||||
{ "capabilities", capabilities },
|
||||
{ "initializationOptions", {} } };
|
||||
|
||||
std::string rootPath = mRootPath;
|
||||
if ( rootPath.empty() ) {
|
||||
if ( !mManager->getLSPWorkspaceFolder().uri.empty() )
|
||||
rootPath = mManager->getLSPWorkspaceFolder().uri.getPath();
|
||||
else
|
||||
rootPath = FileSystem::getCurrentWorkingDirectory();
|
||||
}
|
||||
|
||||
std::string uriRootPath = "file://" + rootPath;
|
||||
params["rootPath"] = rootPath;
|
||||
params["rootUri"] = uriRootPath;
|
||||
params["workspaceFolders"] =
|
||||
toJson( { LSPWorkspaceFolder{ uriRootPath, FileSystem::fileNameFromPath( rootPath ) } } );
|
||||
capabilities["workspace"] = json{ { "workspaceFolders", true }, { "configuration", false } };
|
||||
|
||||
write(
|
||||
newRequest( "initialize", params ),
|
||||
[&]( const json& ) {
|
||||
[&]( const json& resp ) {
|
||||
try {
|
||||
fromJson( mCapabilities, resp["capabilities"] );
|
||||
} catch ( const json::exception& e ) {
|
||||
Log::warning( "LSPClientServer: error parsing capabilities: %s", e.what() );
|
||||
}
|
||||
|
||||
mReady = true;
|
||||
write( newRequest( "initialized" ) );
|
||||
sendQueuedMessages();
|
||||
},
|
||||
[&]( const json& ) {
|
||||
|
||||
} );
|
||||
[&]( const json& ) {} );
|
||||
}
|
||||
|
||||
static TextPosition parsePosition( const json& m ) {
|
||||
auto line = m[MEMBER_LINE].get<int>();
|
||||
auto column = m[MEMBER_CHARACTER].get<int>();
|
||||
return { line, column };
|
||||
}
|
||||
|
||||
static TextRange parseRange( const json& range ) {
|
||||
auto startpos = parsePosition( range[MEMBER_START] );
|
||||
auto endpos = parsePosition( range[MEMBER_END] );
|
||||
return { startpos, endpos };
|
||||
}
|
||||
|
||||
static LSPLocation parseLocation( const json& loc ) {
|
||||
auto uri = URI( loc[MEMBER_URI].get<std::string>() );
|
||||
auto range = parseRange( loc[MEMBER_RANGE] );
|
||||
return { uri, range };
|
||||
}
|
||||
|
||||
static LSPLocation parseLocationLink( const json& loc ) {
|
||||
auto uri = URI( loc[MEMBER_TARGET_URI].get<std::string>() );
|
||||
auto vrange = loc[MEMBER_TARGET_SELECTION_RANGE];
|
||||
if ( vrange.is_null() )
|
||||
vrange = loc[MEMBER_TARGET_RANGE];
|
||||
auto range = parseRange( vrange );
|
||||
return { uri, range };
|
||||
}
|
||||
|
||||
static std::vector<LSPLocation> parseDocumentLocation( const json& result ) {
|
||||
std::vector<LSPLocation> ret;
|
||||
if ( result.is_array() ) {
|
||||
const auto& locs = result;
|
||||
for ( const auto& def : locs ) {
|
||||
const auto& ob = def;
|
||||
ret.push_back( parseLocation( ob ) );
|
||||
if ( ret.back().uri.empty() )
|
||||
ret.back() = parseLocationLink( ob );
|
||||
}
|
||||
} else if ( result.is_object() ) {
|
||||
ret.push_back( parseLocation( result ) );
|
||||
}
|
||||
return ret;
|
||||
void LSPClientServer::sendQueuedMessages() {
|
||||
for ( const auto& msg : mQueuedMessages )
|
||||
write( msg.msg, msg.h, msg.eh );
|
||||
mQueuedMessages.clear();
|
||||
}
|
||||
|
||||
void LSPClientServer::goToLocation( const json& res ) {
|
||||
@@ -461,4 +728,11 @@ LSPClientServer::RequestHandle LSPClientServer::documentImplementation( const UR
|
||||
return getAndGoToLocation( document, pos, "textDocument/implementation" );
|
||||
}
|
||||
|
||||
LSPClientServer::RequestHandle
|
||||
LSPClientServer::didChangeWorkspaceFolders( const std::vector<LSPWorkspaceFolder>& added,
|
||||
const std::vector<LSPWorkspaceFolder>& removed ) {
|
||||
auto params = changeWorkspaceFoldersParams( added, removed );
|
||||
return send( newRequest( "workspace/didChangeWorkspaceFolders", params ) );
|
||||
}
|
||||
|
||||
} // namespace ecode
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#ifndef ECODE_LSPCLIENTSERVER_HPP
|
||||
#ifndef ECODE_LSPCLIENTSERVER_HPP
|
||||
#define ECODE_LSPCLIENTSERVER_HPP
|
||||
|
||||
#include "lspclientprotocol.hpp"
|
||||
#include "lspdefinition.hpp"
|
||||
#include "lspdocumentclient.hpp"
|
||||
#include <eepp/system/process.hpp>
|
||||
@@ -20,11 +21,6 @@ namespace ecode {
|
||||
|
||||
class LSPClientServerManager;
|
||||
|
||||
struct LSPLocation {
|
||||
URI uri;
|
||||
TextRange range;
|
||||
};
|
||||
|
||||
class LSPClientServer {
|
||||
public:
|
||||
template <typename T> using ReplyHandler = std::function<void( const T& )>;
|
||||
@@ -101,6 +97,13 @@ class LSPClientServer {
|
||||
void updateDirty();
|
||||
|
||||
bool hasDocument( TextDocument* doc ) const;
|
||||
|
||||
bool hasDocuments() const;
|
||||
|
||||
LSPClientServer::RequestHandle
|
||||
didChangeWorkspaceFolders( const std::vector<LSPWorkspaceFolder>& added,
|
||||
const std::vector<LSPWorkspaceFolder>& removed );
|
||||
|
||||
protected:
|
||||
LSPClientServerManager* mManager{ nullptr };
|
||||
String::HashType mId;
|
||||
@@ -111,17 +114,31 @@ class LSPClientServer {
|
||||
std::map<TextDocument*, std::unique_ptr<LSPDocumentClient>> mClients;
|
||||
std::map<int, std::pair<GenericReplyHandler, GenericReplyHandler>> mHandlers;
|
||||
Mutex mClientsMutex;
|
||||
bool mReady{ false };
|
||||
struct QueueMessage {
|
||||
json msg;
|
||||
GenericReplyHandler h;
|
||||
GenericReplyHandler eh;
|
||||
};
|
||||
std::vector<QueueMessage> mQueuedMessages;
|
||||
std::string mReceive;
|
||||
std::string mReceiveErr;
|
||||
LSPServerCapabilities mCapabilities;
|
||||
|
||||
int mLastMsgId{ 0 };
|
||||
int mLastMsgId{ -2147483648 };
|
||||
|
||||
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();
|
||||
|
||||
void sendQueuedMessages();
|
||||
|
||||
void processNotification( const json& msg );
|
||||
|
||||
void processRequest( const json& msg );
|
||||
|
||||
@@ -87,16 +87,21 @@ void LSPClientServerManager::tryRunServer( const std::shared_ptr<TextDocument>&
|
||||
} else {
|
||||
server = clientIt->second.get();
|
||||
}
|
||||
if ( server )
|
||||
if ( server ) {
|
||||
if ( !mLSPWorkspaceFolder.uri.empty() )
|
||||
server->didChangeWorkspaceFolders( { mLSPWorkspaceFolder }, {} );
|
||||
server->registerDoc( doc );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LSPClientServerManager::notifyClose( const String::HashType& id ) {
|
||||
auto it = mClients.find( id );
|
||||
if ( it != mClients.end() ) {
|
||||
mClients.erase( it );
|
||||
}
|
||||
void LSPClientServerManager::closeLSPServer( const String::HashType& id ) {
|
||||
mThreadPool->run( [this, id]() {
|
||||
auto it = mClients.find( id );
|
||||
if ( it != mClients.end() ) {
|
||||
mClients.erase( it );
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
void LSPClientServerManager::goToLocation( const LSPLocation& loc ) {
|
||||
@@ -136,8 +141,16 @@ const std::shared_ptr<ThreadPool>& LSPClientServerManager::getThreadPool() const
|
||||
}
|
||||
|
||||
void LSPClientServerManager::updateDirty() {
|
||||
for ( auto& server : mClients )
|
||||
for ( auto& server : mClients ) {
|
||||
server.second->updateDirty();
|
||||
|
||||
if ( !server.second->hasDocuments() )
|
||||
mLSPsToClose.push_back( server.first );
|
||||
}
|
||||
|
||||
if ( !mLSPsToClose.empty() )
|
||||
for ( const auto& server : mLSPsToClose )
|
||||
closeLSPServer( server );
|
||||
}
|
||||
|
||||
void LSPClientServerManager::followSymbolUnderCursor( TextDocument* doc ) {
|
||||
@@ -149,4 +162,15 @@ void LSPClientServerManager::followSymbolUnderCursor( TextDocument* doc ) {
|
||||
}
|
||||
}
|
||||
|
||||
void LSPClientServerManager::didChangeWorkspaceFolders( const std::string& folder ) {
|
||||
mLSPWorkspaceFolder = { "file://" + folder, FileSystem::fileNameFromPath( folder ) };
|
||||
for ( auto& server : mClients ) {
|
||||
server.second->didChangeWorkspaceFolders( { mLSPWorkspaceFolder }, {} );
|
||||
}
|
||||
}
|
||||
|
||||
const LSPWorkspaceFolder& LSPClientServerManager::getLSPWorkspaceFolder() const {
|
||||
return mLSPWorkspaceFolder;
|
||||
}
|
||||
|
||||
} // namespace ecode
|
||||
|
||||
@@ -29,13 +29,19 @@ class LSPClientServerManager {
|
||||
|
||||
void followSymbolUnderCursor( TextDocument* doc );
|
||||
|
||||
protected:
|
||||
void didChangeWorkspaceFolders( const std::string& folder );
|
||||
|
||||
const LSPWorkspaceFolder& getLSPWorkspaceFolder() const;
|
||||
|
||||
protected:
|
||||
friend class LSPClientServer;
|
||||
|
||||
LSPClientPlugin* mPlugin{ nullptr };
|
||||
std::shared_ptr<ThreadPool> mThreadPool;
|
||||
std::map<String::HashType, std::unique_ptr<LSPClientServer>> mClients;
|
||||
std::vector<LSPDefinition> mLSPs;
|
||||
std::vector<String::HashType> mLSPsToClose;
|
||||
LSPWorkspaceFolder mLSPWorkspaceFolder;
|
||||
|
||||
std::vector<LSPDefinition> supportsLSP( const std::shared_ptr<TextDocument>& doc );
|
||||
|
||||
@@ -47,7 +53,7 @@ class LSPClientServerManager {
|
||||
|
||||
void tryRunServer( const std::shared_ptr<TextDocument>& doc );
|
||||
|
||||
void notifyClose( const String::HashType& id );
|
||||
void closeLSPServer( const String::HashType& id );
|
||||
|
||||
void goToLocation( const LSPLocation& loc );
|
||||
};
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include "lspclientserver.hpp"
|
||||
#include <eepp/system/filesystem.hpp>
|
||||
#include <eepp/system/iostreamstring.hpp>
|
||||
#include <eepp/system/log.hpp>
|
||||
|
||||
using namespace EE::System;
|
||||
|
||||
@@ -12,6 +13,8 @@ LSPDocumentClient::LSPDocumentClient( LSPClientServer* server, TextDocument* doc
|
||||
notifyOpen();
|
||||
}
|
||||
|
||||
LSPDocumentClient::~LSPDocumentClient() {}
|
||||
|
||||
void LSPDocumentClient::onDocumentTextChanged() {
|
||||
mModified = true;
|
||||
++mVersion;
|
||||
@@ -35,7 +38,6 @@ void LSPDocumentClient::onDocumentSaved( TextDocument* ) {
|
||||
|
||||
void LSPDocumentClient::onDocumentClosed( TextDocument* ) {
|
||||
mServer->didClose( mDoc );
|
||||
mDoc = nullptr;
|
||||
}
|
||||
|
||||
void LSPDocumentClient::onDocumentDirtyOnFileSystem( TextDocument* ) {}
|
||||
|
||||
@@ -16,6 +16,8 @@ class LSPDocumentClient : public TextDocument::Client {
|
||||
public:
|
||||
LSPDocumentClient( LSPClientServer* server, TextDocument* doc );
|
||||
|
||||
~LSPDocumentClient();
|
||||
|
||||
virtual void onDocumentTextChanged();
|
||||
virtual void onDocumentUndoRedo( const TextDocument::UndoRedo& eventType );
|
||||
virtual void onDocumentCursorChange( const TextPosition& );
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
#include <eepp/ui/uitableview.hpp>
|
||||
#include <eepp/ui/uiwidgetcreator.hpp>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
namespace ecode {
|
||||
|
||||
PluginManager::PluginManager( const std::string& resourcesPath, const std::string& pluginsPath,
|
||||
@@ -38,6 +40,7 @@ bool PluginManager::setEnabled( const std::string& id, bool enable ) {
|
||||
}
|
||||
if ( !enable && plugin != nullptr ) {
|
||||
eeSAFE_DELETE( plugin );
|
||||
mSubscribedPlugins.erase( id );
|
||||
mPlugins.erase( id );
|
||||
}
|
||||
return false;
|
||||
@@ -95,10 +98,36 @@ UICodeEditorSplitter* PluginManager::getSplitter() const {
|
||||
return mSplitter;
|
||||
}
|
||||
|
||||
const std::string& PluginManager::getWorkspaceFolder() const {
|
||||
return mWorkspaceFolder;
|
||||
}
|
||||
|
||||
void PluginManager::setWorkspaceFolder( const std::string& workspaceFolder ) {
|
||||
mWorkspaceFolder = workspaceFolder;
|
||||
sendNotification( Notification::WorkspaceFolderChanged,
|
||||
json{ { "folder", mWorkspaceFolder } } );
|
||||
}
|
||||
|
||||
void PluginManager::subscribeNotifications(
|
||||
UICodeEditorPlugin* plugin,
|
||||
std::function<void( Notification, const nlohmann::json& )> cb ) const {
|
||||
const_cast<PluginManager*>( this )->mSubscribedPlugins[plugin->getId()] = cb;
|
||||
}
|
||||
|
||||
void PluginManager::unsubscribeNotifications( UICodeEditorPlugin* plugin ) const {
|
||||
const_cast<PluginManager*>( this )->mSubscribedPlugins.erase( plugin->getId() );
|
||||
}
|
||||
|
||||
void PluginManager::setSplitter( UICodeEditorSplitter* splitter ) {
|
||||
mSplitter = splitter;
|
||||
}
|
||||
|
||||
void PluginManager::sendNotification( const Notification& notification,
|
||||
const nlohmann::json& json ) {
|
||||
for ( const auto& plugin : mSubscribedPlugins )
|
||||
plugin.second( notification, json );
|
||||
}
|
||||
|
||||
bool PluginManager::hasDefinition( const std::string& id ) {
|
||||
return mDefinitions.find( id ) != mDefinitions.end();
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <eepp/ui/uiscenenode.hpp>
|
||||
#include <eepp/ui/uiwindow.hpp>
|
||||
#include <memory>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <string>
|
||||
|
||||
using namespace EE;
|
||||
@@ -56,6 +57,8 @@ struct PluginDefinition {
|
||||
|
||||
class PluginManager {
|
||||
public:
|
||||
enum Notification { WorkspaceFolderChanged };
|
||||
|
||||
static constexpr int versionNumber( int major, int minor, int patch ) {
|
||||
return ( (major)*1000 + (minor)*100 + ( patch ) );
|
||||
}
|
||||
@@ -97,19 +100,34 @@ class PluginManager {
|
||||
|
||||
UICodeEditorSplitter* getSplitter() const;
|
||||
|
||||
const std::string& getWorkspaceFolder() const;
|
||||
|
||||
void setWorkspaceFolder( const std::string& workspaceFolder );
|
||||
|
||||
void
|
||||
subscribeNotifications( UICodeEditorPlugin* plugin,
|
||||
std::function<void( Notification, const nlohmann::json& )> cb ) const;
|
||||
|
||||
void unsubscribeNotifications( UICodeEditorPlugin* plugin ) const;
|
||||
|
||||
protected:
|
||||
friend class App;
|
||||
std::string mResourcesPath;
|
||||
std::string mPluginsPath;
|
||||
std::string mWorkspaceFolder;
|
||||
std::map<std::string, UICodeEditorPlugin*> mPlugins;
|
||||
std::map<std::string, bool> mPluginsEnabled;
|
||||
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;
|
||||
|
||||
bool hasDefinition( const std::string& id );
|
||||
|
||||
void setSplitter( UICodeEditorSplitter* splitter );
|
||||
|
||||
void sendNotification( const Notification& notification, const nlohmann::json& json = {} );
|
||||
};
|
||||
|
||||
class PluginsModel : public Model {
|
||||
|
||||
Reference in New Issue
Block a user