loom
416 строк · 15.3 Кб
1#include "simodo/lsp-server/ServerContext.h"
2#include "simodo/lsp-server/DocumentContext.h"
3#include "TaskDistribute.h"
4#include "TaskAnalyze.h"
5
6#include "simodo/LibVersion.h"
7
8#include "simodo/variable/json/Serialization.h"
9#include "simodo/inout/convert/functions.h"
10
11#include <thread>
12#include <iostream>
13#include <chrono>
14#include <iomanip>
15#include <filesystem>
16
17namespace simodo::lsp
18{
19
20inline const std::string content_length_const = "Content-Length: ";
21
22namespace
23{
24std::pair<int, std::string> readHeader(std::istream & is)
25{
26/// @todo Необходимо заменить на работу с boost::asio, чтобы не было клинча в ожиданиях.
27/// Это когда клиент ждёт, когда сервер завершиться после нотификации о завершении, а
28/// сервер в ожидании очередной команды. Или придумать что-то ещё.
29std::string header;
30std::string line;
31int content_size = 0;
32
33while(std::getline(is,line) && !line.empty()) {
34if (line.find(content_length_const) == 0)
35content_size = std::stol(line.substr(content_length_const.size()));
36header += line + "\n";
37}
38
39return {content_size, header};
40}
41
42std::string readContent(std::istream & is, int content_length)
43{
44std::string content;
45
46for(int i=0; i < content_length && is.good(); ++i)
47content += is.get();
48
49return content;
50}
51
52bool verifyJson(const std::string & str)
53{
54return !str.empty() && str[0] == '{' && str[str.size()-1] == '}';
55}
56
57bool verifyExit(const std::string & json_str)
58{
59const std::string exit_request_string = "\"method\":\"exit\"";
60
61if (json_str.length() > 4*exit_request_string.length())
62return false;
63
64return json_str.find(exit_request_string) != std::string::npos;
65}
66}
67
68ServerContext::ServerContext(std::u16string server_name,
69lsp::ServerCapabilities server_capabilities,
70lsp::SimodoCapabilities simodo_capabilities,
71DocumentOperationFactory_interface & document_operation_factory,
72inout::Logger_interface & log,
73std::istream & is,
74std::ostream & os,
75bool save_mode)
76: _server_name(server_name)
77, _server_capabilities(server_capabilities)
78, _simodo_capabilities(simodo_capabilities)
79, _document_operation_factory(document_operation_factory)
80, _log(log)
81, _is(is)
82, _os(os)
83, _queue_for_sending()
84, _tp(save_mode ? 0 : 4)
85, _response_thread(&ServerContext::sendResponse_loop, this)
86, _m()
87, _save_mode(save_mode)
88{
89}
90
91ServerContext::~ServerContext()
92{
93_done = true;
94_tp.stop();
95_response_thread.join();
96}
97
98bool ServerContext::openDocument(const simodo::variable::Object & doc_params)
99{
100const variable::Value & textDocument_value = doc_params.find(u"textDocument");
101if (textDocument_value.type() != variable::ValueType::Object)
102return false;
103
104const variable::Value & uri_value = textDocument_value.getObject()->find(u"uri");
105if (uri_value.type() != variable::ValueType::String)
106return false;
107
108const variable::Value & languageId_value = textDocument_value.getObject()->find(u"languageId");
109if (languageId_value.type() != variable::ValueType::String)
110return false;
111
112std::string uri = inout::toU8(uri_value.getString());
113std::string languageId = inout::toU8(languageId_value.getString());
114std::unique_lock documents_locker(_documents_mutex);
115auto it = _documents.find(uri);
116
117if (it != _documents.end()) {
118if (it->second->is_opened())
119return false;
120
121if (!it->second->open(*textDocument_value.getObject()))
122return false;
123}
124else {
125std::unique_ptr<DocumentContext> doc
126= std::make_unique<DocumentContext>(
127*this,
128languageId,
129*textDocument_value.getObject());
130
131if (!doc->valid())
132return false;
133
134auto [it_doc,ok] = _documents.insert({uri,std::move(doc)});
135it = it_doc;
136}
137
138documents_locker.unlock();
139
140/// @attention Попавшие в контейнер документы нельзя оттуда удалять!
141tp().submit(new TaskAnalyze(*it->second));
142
143return true;
144}
145
146bool ServerContext::changeDocument(const simodo::variable::Object & doc_params)
147{
148const variable::Value & textDocument_value = doc_params.find(u"textDocument");
149if (textDocument_value.type() != variable::ValueType::Object) {
150log().error("ServerContext::changeDocument: Wrong params structure #1");
151return false;
152}
153
154const variable::Value & uri_value = textDocument_value.getObject()->find(u"uri");
155if (uri_value.type() != variable::ValueType::String) {
156log().error("ServerContext::changeDocument: Wrong params structure #2");
157return false;
158}
159
160std::string uri = inout::toU8(uri_value.getString());
161std::lock_guard documents_locker(_documents_mutex);
162auto it = _documents.find(uri);
163if (it == _documents.end()) {
164log().error("ServerContext::changeDocument: Document '" + uri + "' not found");
165return false;
166}
167
168if (!it->second->change(doc_params)) {
169log().error("ServerContext::changeDocument: doc->change return false");
170return false;
171}
172
173/// @attention Попавшие в контейнер документы нельзя оттуда удалять!
174log().debug("ServerContext::changeDocument: start analyze for '" + it->second->file_name() + "'");
175tp().submit(new TaskAnalyze(*it->second));
176
177for(auto & [doc_uri,p_doc] : _documents)
178if (doc_uri != uri && p_doc->is_opened() && p_doc->checkDependency(uri)) {
179log().debug("ServerContext::changeDocument: start analyze for '" + p_doc->file_name() + "'");
180tp().submit(new TaskAnalyze(*p_doc));
181}
182
183return true;
184}
185
186bool ServerContext::closeDocument(const std::string & uri)
187{
188std::lock_guard locker(_documents_mutex);
189auto it = _documents.find(uri);
190
191if (it == _documents.end())
192return false;
193
194return it->second->close();
195}
196
197DocumentContext * ServerContext::findDocument(const std::string & uri)
198{
199std::lock_guard locker(_documents_mutex);
200
201auto it = _documents.find(uri);
202
203return it != _documents.end() ? it->second.get() : nullptr;
204}
205
206const DocumentContext * ServerContext::findDocument(const std::string & uri) const
207{
208std::lock_guard locker(_documents_mutex);
209
210auto it = _documents.find(uri);
211
212return it != _documents.end() ? it->second.get() : nullptr;
213}
214
215bool ServerContext::findAndCopy(const std::string & uri, std::u16string & content)
216{
217std::lock_guard locker(_documents_mutex);
218auto it = _documents.find(uri);
219
220if (it == _documents.end())
221return false;
222
223it->second->copyContent(content);
224return true;
225}
226
227variable::JsonRpc ServerContext::initialize(lsp::ClientParams params, int id)
228{
229_client_params = params;
230
231using namespace variable;
232
233if (_client_params.rootPath.empty())
234_client_params.rootPath = std::filesystem::current_path().string();
235else {
236std::error_code ec;
237std::filesystem::path work_dir = _client_params.rootPath;
238std::filesystem::current_path(work_dir, ec);
239if (ec)
240/// @todo Скорректировать коды (и тексты) ошибок
241return JsonRpc(-1, u"Unable to set work dir '" + inout::toU16(_client_params.rootPath) + u"'. "
242+ inout::toU16(ec.message()), id);
243}
244
245std::vector<Value> triggerCharacters;
246for (std::u16string & s : _server_capabilities.completionProvider.triggerCharacters)
247triggerCharacters.push_back(s);
248
249std::vector<Value> tokenTypes;
250for (std::u16string & s : _server_capabilities.semanticTokensProvider.legend.tokenTypes)
251tokenTypes.push_back(s);
252
253std::vector<Value> tokenModifiers;
254for (std::u16string & s : _server_capabilities.semanticTokensProvider.legend.tokenModifiers)
255tokenModifiers.push_back(s);
256
257std::vector<Value> simodoRunnerOptions;
258for (const SimodoRunnerOption & opt : _simodo_capabilities.runnerOptions)
259simodoRunnerOptions.push_back(Object {{
260{u"languageID", opt.languageID},
261{u"viewName", opt.viewName}
262}});
263
264return JsonRpc(Value { Object {{
265{u"capabilities", Object {{
266// The position encoding the server picked from the encodings offered by the
267// client via the client capability `general.positionEncodings`.
268{u"positionEncoding", POSITION_ENCODING_UTF16},
269// Documents are synced by always sending the full content of the document.
270{u"textDocumentSync", static_cast<int64_t>(TextDocumentSyncKind::Full)},
271// The server provides completion support.
272{u"completionProvider", Object {{
273{u"triggerCharacters", Array {triggerCharacters}},
274// The server provides support to resolve additional information for a completion item.
275{u"resolveProvider", _server_capabilities.completionProvider.resolveProvider},
276// The server supports the following `CompletionItem` specific capabilities.
277{u"completionItem", Object {{
278// The server has support for completion item label details (see also
279// `CompletionItemLabelDetails`) when receiving a completion item in a
280// resolve call.
281{u"labelDetailsSupport",_server_capabilities.completionProvider.completionItem.labelDetailsSupport},
282}}},
283}}},
284// The server provides hover support.
285{u"hoverProvider", _server_capabilities.hoverProvider},
286// The server provides go to declaration support.
287{u"declarationProvider", _server_capabilities.declarationProvider},
288// The server provides goto definition support.
289{u"definitionProvider", _server_capabilities.definitionProvider},
290// The server provides document symbol support.
291{u"documentSymbolProvider", _server_capabilities.documentSymbolProvider},
292// The server provides semantic tokens support.
293{u"semanticTokensProvider", Object {{
294{u"legend", Object {{
295{u"tokenTypes", Array {tokenTypes}},
296{u"tokenModifiers", Array {tokenModifiers}},
297}}},
298// Server supports providing semantic tokens for a full document.
299{u"full", true},
300}}},
301}}},
302{u"simodo", Object {{
303{u"runnerProvider", _simodo_capabilities.runnerProvider},
304{u"debugProvider", _simodo_capabilities.debugProvider},
305{u"runnerOptions", Array {simodoRunnerOptions}},
306}}},
307{u"serverInfo", Object {{
308{u"name", u"SIMODO/loom " + _server_name},
309{u"version", inout::toU16(
310std::to_string(static_cast<int>(lib_version().major()))+"."+
311std::to_string(static_cast<int>(lib_version().minor())))},
312}}},
313}}}, id);
314}
315
316void ServerContext::initialized()
317{
318_state = ServerState::Work;
319}
320
321bool ServerContext::shutdown()
322{
323_state = ServerState::Shutdown;
324/// @todo Выполнить необходимые действия для сохранения состояния, если это нужно
325return true;
326}
327
328void ServerContext::exit()
329{
330_done = true;
331}
332
333bool ServerContext::run()
334{
335bool ok = true;
336
337while(!is_done()) {
338auto [content_length, header] = readHeader(_is);
339if (header.empty() || content_length <= 0) {
340/// @todo Скорректировать коды (и тексты) ошибок
341_queue_for_sending.push(simodo::variable::JsonRpc(-1, u"Wrong header structure", -1));
342_log.critical("Wrong header structure", header);
343ok = false;
344break;
345}
346{
347std::ostringstream so;
348so << std::endl
349<< "RECEIVE FROM CLIENT " << std::endl
350<< "'" << header << "'" << std::endl;
351_log.debug(so.str());
352}
353
354std::string content = readContent(_is, content_length);
355if (content.empty()) {
356/// @todo Скорректировать коды (и тексты) ошибок
357_queue_for_sending.push(simodo::variable::JsonRpc(-1, u"Wrong content length", -1));
358_log.critical("Wrong content structure", content);
359ok = false;
360break;
361}
362{
363std::ostringstream so;
364so << content << std::endl;
365_log.debug("Content of JSON-RPC", so.str());
366}
367
368if (!verifyJson(content)) {
369/// @todo Скорректировать коды (и тексты) ошибок
370_queue_for_sending.push(simodo::variable::JsonRpc(-1, u"Wrong JSON structure", -1));
371_log.critical("Wrong JSON structure", content);
372ok = false;
373break;
374}
375
376/// @attention Завершающую нотификацию сделал сразу при входном анализе запросов клиента,
377/// чтобы корректно завершить работу
378if (verifyExit(content)) {
379_log.info("EXIT");
380exit();
381break;
382}
383
384tp().submit(new TaskDistribute(*this, content));
385
386if (save_mode())
387std::this_thread::sleep_for(std::chrono::milliseconds(500));
388}
389
390/// @todo Дожидаться отправки всех сообщений? Вроде бы не нужно, т.к. протокол
391/// исключает такую необходимость.
392return ok;
393}
394
395void ServerContext::sendResponse_loop()
396{
397/// @attention Очереди входящих запросов, как и исходящих ответов
398/// при завершение работы могут оставаться не пустыми.
399while(!_done) {
400variable::JsonRpc response;
401if (_queue_for_sending.pop_or_timeout(response, 100)) {
402std::string json_string = variable::toJson(response.value(), true, true);
403std::ostringstream log_str;
404log_str << std::endl
405<< "SEND TO CLIENT " << std::endl
406<< content_length_const << json_string.length() << std::endl << std::endl
407<< json_string;
408_log.debug(log_str.str());
409_os << content_length_const << json_string.length() << std::endl << std::endl
410<< json_string;
411_os.flush();
412}
413}
414}
415
416}