loom
474 строки · 19.5 Кб
1#include "simodo/lsp/client/LanguageClient.h"2
3#include <boost/asio.hpp>4#ifdef CROSS_WIN5#include <boost/process/windows.hpp>6#endif7
8namespace simodo::lsp9{
10using namespace simodo::inout;11
12inline const std::string content_length_const = "Content-Length: ";13
14LanguageClient::LanguageClient(const std::string & process_path,15const std::vector<std::string> & process_args,16const variable::Value & initialize_params,17inout::Logger_interface & logger,18ServerRestartSupport_interface * )19: _process_path(process_path)20, _process_args(process_args)21, _initialize_params(initialize_params)22, _log(logger)23// , _server_restart_support(server_restart_support)24{25_ok = start();26
27if (!_ok)28close();29}30
31LanguageClient::~LanguageClient()32{33close();34if (_sending_thread)35_sending_thread->join();36if (_response_thread)37_response_thread->join();38}39
40InitializeResult LanguageClient::initialize_result() const41{42std::lock_guard locker(_initialize_result_mutex);43return _initialize_result;44}45
46ServerCapabilities LanguageClient::server_capabilities() const47{48std::lock_guard locker(_initialize_result_mutex);49return _initialize_result.capabilities;50}51
52SimodoCapabilities LanguageClient::simodo_capabilities() const53{54std::lock_guard locker(_initialize_result_mutex);55return _initialize_result.simodo;56}57
58int64_t LanguageClient::exec(const std::string method, variable::Value params, RpcHandle handle, const void * sender)59{60std::unique_ptr<variable::JsonRpc> rpc;61
62if (handle)63rpc = std::make_unique<variable::JsonRpc>(toU16(method), params, ++_last_id);64else65rpc = std::make_unique<variable::JsonRpc>(toU16(method), params);66
67return exec(*rpc, handle, sender);68}69
70bool LanguageClient::done(uint64_t timeout_mills)71{72if (!running())73return true;74
75uint64_t elapsed_mills = 0;76
77while(elapsed_mills < timeout_mills) {78if (_queue_for_sending.empty()) {79std::this_thread::sleep_for(std::chrono::milliseconds(500));80if (_queue_for_sending.empty())81return true;82}83else84std::this_thread::sleep_for(std::chrono::milliseconds(500));85
86elapsed_mills += 500;87}88
89return false;90}91
92void LanguageClient::close()93{94if (_stopping)95return;96
97if (_language_server) {98if (waitResponse(exec("shutdown", {}, [](variable::JsonRpc, const void *){})))99exec("exit", {});100
101_stopping = true;102
103std::error_code ec;104// @todo Перейти на boost::process::v2105// _language_server->wait_for(std::chrono::milliseconds(100), ec);106// if (ec)107// _log.error("Language server process stopping error", ec.message());108std::this_thread::sleep_for(std::chrono::milliseconds(100));109
110if (_language_server->running(ec))111{112_language_server->terminate(ec);113}114_language_server->wait(ec);115}116}117
118bool LanguageClient::registerListener(std::string method, RpcHandle listener)119{120std::lock_guard locker(_commands_listeners_mutex);121_commands_listeners[method] = listener;122return true;123}124
125// ------------------------------------------------------------------------------------------126// private ----------------------------------------------------------------------------------127// ------------------------------------------------------------------------------------------128
129bool LanguageClient::start()130{131bool ok = true;132
133try {134_language_server = std::make_unique<bp::child>(135_process_path
136, _process_args137, bp::std_in < _os138, bp::std_out > _is139#ifdef CROSS_WIN140, bp::windows::hide141#endif142, bp::on_exit([&](auto...)143{144_is.close();145_os.close();146_log.info("Server stoped");147_ok = false;148}149));150
151if (!_language_server->running()) {152_log.critical("Unable to run language server (stop)");153std::error_code ec;154_language_server->terminate(ec);155_language_server.reset();156ok = false;157}158}159catch(const std::exception& e) {160_log.critical("Unable to run language server (stop)", e.what());161_language_server.reset();162ok = false;163}164
165if (ok) {166_sending_thread = std::make_unique<std::thread>(& LanguageClient::sender, this);167_response_thread = std::make_unique<std::thread>(& LanguageClient::response_listener, this);168
169int64_t id = exec("initialize", _initialize_params,170[this](variable::JsonRpc rpc, const void *){171if (!rpc.is_valid()) {172_log.critical("Initialize result error", rpc.origin());173return;174}175std::lock_guard locker(_initialize_result_mutex);176_initialize_result = parseInitializeResult(rpc.result());177});178
179if (id > 0) {180if (waitResponse(id))181exec("initialized", {});182else {183_log.critical("Response timeout occurred (stop)");184ok = false;185}186}187else {188_log.critical("Response sending error occurred (stop)");189ok = false;190}191}192
193return ok;194}195
196int64_t LanguageClient::exec(const variable::JsonRpc & rpc, RpcHandle handle, const void * sender)197{198if (!_os.good() || !rpc.is_valid())199return -1;200
201int64_t id = rpc.id();202
203if ((id > 0 && handle == nullptr) || (id == -1 && handle != nullptr))204return -1;205
206std::string json = variable::toJson(rpc.value(), true, true);207_queue_for_sending.push(json);208
209if (id > 0) {210std::lock_guard locker(_commands_waiting_to_be_executed_mutex);211_commands_waiting_to_be_executed.insert({id, {handle,sender}});212}213
214return id;215}216
217void LanguageClient::sender()218try219{220_log.debug("Output pipe sender starting");221while(_ok && !_stopping) {222std::string json;223bool got = _queue_for_sending.pop_or_timeout(json,100);224
225if (got) {226if (_os.good()) {227_os << content_length_const << json.length()228<< "\n\n"229<< json;230_os.flush();231}232else {233_log.critical("Bad pipe state has occurred during send operation");234_ok = false;235break;236}237}238}239
240_log.debug("Output pipe sender ending");241}242catch(const std::exception & e)243{244_log.critical("Exception has occurred during send operation", e.what());245_ok = false;246}247catch(...)248{249_log.critical("Exception has occurred during send operation");250_ok = false;251}252
253void LanguageClient::response_listener()254{255_log.debug("Input pipe listener starting");256while(_ok && !_stopping) {257std::string line;258size_t content_length = 0;259auto is_empty = [](const std::string & line) -> bool260{261return line.empty()262|| (263line.size() == 1264&& line.back() == '\r'265);266};267while(_is.good() && std::getline(_is,line) && !is_empty(line)) {268if (line.find(content_length_const) == 0)269content_length = std::stol(line.substr(content_length_const.size()));270}271
272if (!_is.good()) {273// Штатная ситуация274// _log.info("Wrong input pipe state (stop listening)");275_ok = false;276break;277}278_log.debug("Ready receive content length " + std::to_string(content_length));279
280std::string content;281for(size_t i=0; i < content_length && _is.good(); ++i)282content += _is.get();283
284_log.debug("Ready work with content", content);285
286if (content.length() != content_length || !verifyJson(content)) {287_log.error("Wrong JSON-RPC content (try to recover input pipe)", content);288continue;289}290
291RpcHandle handle = nullptr;292const void * sender = nullptr;293const variable::JsonRpc rpc(content);294if (!rpc.is_valid()) {295_log.error("JSON-RPC structure is invalid", content);296}297else if (!rpc.method().empty()) {298{299std::lock_guard locker(_commands_listeners_mutex);300auto it = _commands_listeners.find(toU8(rpc.method()));301if (it == _commands_listeners.end())302_log.warning("An command or notification was ignored", content);303else304handle = it->second;305}306if (handle)307handle(rpc, nullptr);308}309else {310{311std::lock_guard locker(_commands_waiting_to_be_executed_mutex);312auto it = _commands_waiting_to_be_executed.find(rpc.id());313if (it != _commands_waiting_to_be_executed.end()) {314handle = it->second.first;315sender = it->second.second;316_commands_waiting_to_be_executed.erase(it);317}318else319_log.error("An unexpected response was received", content);320}321if (handle) {322handle(rpc, sender);323_command_complete_condition.notify_one();324}325}326}327_log.debug("Input pipe listener ending");328}329
330bool LanguageClient::waitResponse(int64_t id, int timeout_mills)331{332if (id <= 0)333return false;334
335std::unique_lock locker(_command_complete_condition_mutex);336return _command_complete_condition.wait_for(locker, std::chrono::milliseconds(timeout_mills), [&] {337std::lock_guard locker(_commands_waiting_to_be_executed_mutex);338return _commands_waiting_to_be_executed.find(id) == _commands_waiting_to_be_executed.end();339});340}341
342bool LanguageClient::verifyJson(const std::string & str)343{344return !str.empty() && str[0] == '{' && str[str.size()-1] == '}';345}346
347InitializeResult LanguageClient::parseInitializeResult(const variable::Value & result_value)348{349InitializeResult res;350
351if (result_value.type() != variable::ValueType::Object) {352_log.error("LanguageClient::parseInitializeResult: unable to parse result 1");353return res;354}355
356const variable::Value & capabilities_value = result_value.getObject()->find(u"capabilities");357const variable::Value & simodo_value = result_value.getObject()->find(u"simodo");358const variable::Value & serverInfo_value = result_value.getObject()->find(u"serverInfo");359
360if (capabilities_value.type() != variable::ValueType::Object) {361_log.error("LanguageClient::parseInitializeResult: unable to parse result 2");362return res;363}364
365const variable::Value & positionEncoding_value = capabilities_value.getObject()->find(u"positionEncoding");366const variable::Value & textDocumentSync_value = capabilities_value.getObject()->find(u"textDocumentSync");367const variable::Value & hoverProvider_value = capabilities_value.getObject()->find(u"hoverProvider");368const variable::Value & declarationProvider_value=capabilities_value.getObject()->find(u"declarationProvider");369const variable::Value & definitionProvider_value= capabilities_value.getObject()->find(u"definitionProvider");370const variable::Value & referencesProvider_value= capabilities_value.getObject()->find(u"referencesProvider");371const variable::Value & documentSymbolProvider_value = capabilities_value.getObject()->find(u"documentSymbolProvider");372const variable::Value & semanticTokensProvider_value = capabilities_value.getObject()->find(u"semanticTokensProvider");373const variable::Value & completionProvider_value = capabilities_value.getObject()->find(u"completionProvider");374
375if (positionEncoding_value.type() == variable::ValueType::String)376res.capabilities.positionEncoding = positionEncoding_value.getString();377
378if (textDocumentSync_value.type() == variable::ValueType::Object) {379const variable::Value & openClose_value = textDocumentSync_value.getObject()->find(u"openClose");380const variable::Value & change_value = textDocumentSync_value.getObject()->find(u"change");381
382if (openClose_value.type() == variable::ValueType::Bool)383res.capabilities.textDocumentSync.openClose = openClose_value.getBool();384if (change_value.type() == variable::ValueType::Int)385res.capabilities.textDocumentSync.change = static_cast<TextDocumentSyncKind>(change_value.getInt());386}387
388if (hoverProvider_value.type() == variable::ValueType::Bool)389res.capabilities.hoverProvider = hoverProvider_value.getBool();390if (declarationProvider_value.type() == variable::ValueType::Bool)391res.capabilities.declarationProvider = declarationProvider_value.getBool();392if (definitionProvider_value.type() == variable::ValueType::Bool)393res.capabilities.definitionProvider = definitionProvider_value.getBool();394if (referencesProvider_value.type() == variable::ValueType::Bool)395res.capabilities.referencesProvider = referencesProvider_value.getBool();396if (documentSymbolProvider_value.type() == variable::ValueType::Bool)397res.capabilities.documentSymbolProvider = documentSymbolProvider_value.getBool();398
399if (semanticTokensProvider_value.type() == variable::ValueType::Object) {400const variable::Value & legend_value = semanticTokensProvider_value.getObject()->find(u"legend");401if (legend_value.type() == variable::ValueType::Object) {402const variable::Value & tokenTypes_value = legend_value.getObject()->find(u"tokenTypes");403const variable::Value & tokenModifiers_value = legend_value.getObject()->find(u"tokenModifiers");404
405if (tokenTypes_value.type() == variable::ValueType::Array)406for(auto & v : tokenTypes_value.getArray()->values())407if (v.type() == variable::ValueType::String)408res.capabilities.semanticTokensProvider.legend.tokenTypes.push_back(v.getString());409
410if (tokenModifiers_value.type() == variable::ValueType::Array)411for(auto & v : tokenModifiers_value.getArray()->values())412if (v.type() == variable::ValueType::String)413res.capabilities.semanticTokensProvider.legend.tokenModifiers.push_back(v.getString());414}415}416
417if (completionProvider_value.isObject()) {418const variable::Value & triggerCharacters_value = completionProvider_value.getObject()->find(u"triggerCharacters");419const variable::Value & resolveProvider_value = completionProvider_value.getObject()->find(u"resolveProvider");420const variable::Value & completionItem_value = completionProvider_value.getObject()->find(u"completionItem");421
422if (triggerCharacters_value.isArray()) {423const std::shared_ptr<variable::Array> triggerCharacters_array = triggerCharacters_value.getArray();424for(const variable::Value & v : triggerCharacters_array->values())425if (v.isString())426res.capabilities.completionProvider.triggerCharacters.push_back(v.getString());427}428
429if (resolveProvider_value.isBoolean())430res.capabilities.completionProvider.resolveProvider = resolveProvider_value.getBool();431
432if (completionItem_value.isObject()) {433const variable::Value & labelDetailsSupport_value = completionItem_value.getObject()->find(u"labelDetailsSupport");434if (labelDetailsSupport_value.isBoolean())435res.capabilities.completionProvider.completionItem.labelDetailsSupport = labelDetailsSupport_value.getBool();436}437}438
439if (simodo_value.type() == variable::ValueType::Object) {440const variable::Value & runnerProvider_value = simodo_value.getObject()->find(u"runnerProvider");441const variable::Value & debugProvider_value = simodo_value.getObject()->find(u"debugProvider");442const variable::Value & runnerOptions_value = simodo_value.getObject()->find(u"runnerOptions");443
444if (runnerProvider_value.type() == variable::ValueType::Bool)445res.simodo.runnerProvider = runnerProvider_value.getBool();446if (debugProvider_value.type() == variable::ValueType::Bool)447res.simodo.debugProvider = debugProvider_value.getBool();448if (runnerOptions_value.isArray()) {449const std::shared_ptr<variable::Array> runnerOptions_array = runnerOptions_value.getArray();450for(const variable::Value & opt_value : runnerOptions_array->values())451if (opt_value.isObject()) {452const variable::Value & languageID_value = opt_value.getObject()->find(u"languageID");453const variable::Value & viewName_value = opt_value.getObject()->find(u"viewName");454
455if (languageID_value.isString() && viewName_value.isString())456res.simodo.runnerOptions.push_back({languageID_value.getString(), viewName_value.getString()});457}458}459}460
461if (serverInfo_value.type() == variable::ValueType::Object) {462const variable::Value & name_value = serverInfo_value.getObject()->find(u"name");463const variable::Value & version_value = serverInfo_value.getObject()->find(u"version");464
465if (name_value.type() == variable::ValueType::String)466res.serverInfo.name = name_value.getString();467if (version_value.type() == variable::ValueType::String)468res.serverInfo.version = version_value.getString();469}470
471return res;472}473
474}