loom

Форк
0
/
ServerContext.cpp 
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

17
namespace simodo::lsp
18
{
19

20
inline const std::string content_length_const = "Content-Length: ";
21

22
namespace 
23
{
24
    std::pair<int, std::string> readHeader(std::istream & is)
25
    {
26
        /// @todo Необходимо заменить на работу с boost::asio, чтобы не было клинча в ожиданиях.
27
        /// Это когда клиент ждёт, когда сервер завершиться после нотификации о завершении, а 
28
        /// сервер в ожидании очередной команды. Или придумать что-то ещё.
29
        std::string header;
30
        std::string line;
31
        int         content_size = 0;
32

33
        while(std::getline(is,line) && !line.empty()) {
34
            if (line.find(content_length_const) == 0)
35
                content_size = std::stol(line.substr(content_length_const.size()));
36
            header += line + "\n";
37
        }
38

39
        return {content_size, header};
40
    }
41

42
    std::string readContent(std::istream & is, int content_length)
43
    {
44
        std::string content;
45

46
        for(int i=0; i < content_length && is.good(); ++i)
47
            content += is.get();
48

49
        return content;
50
    }
51

52
    bool verifyJson(const std::string & str)
53
    {
54
        return !str.empty() && str[0] == '{' && str[str.size()-1] == '}';
55
    }
56

57
    bool verifyExit(const std::string & json_str)
58
    {
59
        const std::string exit_request_string = "\"method\":\"exit\"";
60

61
        if (json_str.length() > 4*exit_request_string.length())
62
            return false;
63

64
        return json_str.find(exit_request_string) != std::string::npos;
65
    }
66
}
67

68
ServerContext::ServerContext(std::u16string server_name,
69
                             lsp::ServerCapabilities server_capabilities,
70
                             lsp::SimodoCapabilities simodo_capabilities,
71
                             DocumentOperationFactory_interface & document_operation_factory,
72
                             inout::Logger_interface & log, 
73
                             std::istream & is, 
74
                             std::ostream & os, 
75
                             bool 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

91
ServerContext::~ServerContext()
92
{
93
    _done = true;
94
    _tp.stop();
95
    _response_thread.join();
96
}
97

98
bool ServerContext::openDocument(const simodo::variable::Object & doc_params)
99
{
100
    const variable::Value & textDocument_value = doc_params.find(u"textDocument");
101
    if (textDocument_value.type() != variable::ValueType::Object)
102
        return false;
103

104
    const variable::Value & uri_value = textDocument_value.getObject()->find(u"uri");
105
    if (uri_value.type() != variable::ValueType::String)
106
        return false;
107

108
    const variable::Value & languageId_value = textDocument_value.getObject()->find(u"languageId");
109
    if (languageId_value.type() != variable::ValueType::String)
110
        return false;
111

112
    std::string         uri         = inout::toU8(uri_value.getString());
113
    std::string         languageId  = inout::toU8(languageId_value.getString());
114
    std::unique_lock    documents_locker(_documents_mutex);
115
    auto                it          = _documents.find(uri);
116

117
    if (it != _documents.end()) {
118
        if (it->second->is_opened())
119
            return false;
120

121
        if (!it->second->open(*textDocument_value.getObject()))
122
            return false;
123
    }
124
    else {
125
        std::unique_ptr<DocumentContext> doc 
126
                        = std::make_unique<DocumentContext>(
127
                                            *this,
128
                                            languageId,
129
                                            *textDocument_value.getObject());
130

131
        if (!doc->valid())
132
            return false;
133

134
        auto [it_doc,ok] = _documents.insert({uri,std::move(doc)});
135
        it = it_doc;
136
    }
137

138
    documents_locker.unlock();
139

140
    /// @attention Попавшие в контейнер документы нельзя оттуда удалять!
141
    tp().submit(new TaskAnalyze(*it->second));
142

143
    return true;
144
}
145

146
bool ServerContext::changeDocument(const simodo::variable::Object & doc_params)
147
{
148
    const variable::Value & textDocument_value = doc_params.find(u"textDocument");
149
    if (textDocument_value.type() != variable::ValueType::Object) {
150
        log().error("ServerContext::changeDocument: Wrong params structure #1");
151
        return false;
152
    }
153

154
    const variable::Value & uri_value = textDocument_value.getObject()->find(u"uri");
155
    if (uri_value.type() != variable::ValueType::String) {
156
        log().error("ServerContext::changeDocument: Wrong params structure #2");
157
        return false;
158
    }
159

160
    std::string     uri   = inout::toU8(uri_value.getString());
161
    std::lock_guard documents_locker(_documents_mutex);
162
    auto            it    = _documents.find(uri);
163
    if (it == _documents.end()) {
164
        log().error("ServerContext::changeDocument: Document '" + uri + "' not found");
165
        return false;
166
    }
167

168
    if (!it->second->change(doc_params)) {
169
        log().error("ServerContext::changeDocument: doc->change return false");
170
        return false;
171
    }
172

173
    /// @attention Попавшие в контейнер документы нельзя оттуда удалять!
174
    log().debug("ServerContext::changeDocument: start analyze for '" + it->second->file_name() + "'");
175
    tp().submit(new TaskAnalyze(*it->second));
176

177
    for(auto & [doc_uri,p_doc] : _documents)
178
        if (doc_uri != uri && p_doc->is_opened() && p_doc->checkDependency(uri)) {
179
            log().debug("ServerContext::changeDocument: start analyze for '" + p_doc->file_name() + "'");
180
            tp().submit(new TaskAnalyze(*p_doc));
181
        }
182

183
    return true;
184
}
185

186
bool ServerContext::closeDocument(const std::string & uri)
187
{
188
    std::lock_guard locker(_documents_mutex);
189
    auto it = _documents.find(uri);
190

191
    if (it == _documents.end())
192
        return false;
193

194
    return it->second->close();
195
}
196

197
DocumentContext * ServerContext::findDocument(const std::string & uri) 
198
{
199
    std::lock_guard locker(_documents_mutex);
200

201
    auto it = _documents.find(uri);
202
    
203
    return it != _documents.end() ? it->second.get() : nullptr;
204
}
205

206
const DocumentContext * ServerContext::findDocument(const std::string & uri) const
207
{
208
    std::lock_guard locker(_documents_mutex);
209

210
    auto it = _documents.find(uri);
211
    
212
    return it != _documents.end() ? it->second.get() : nullptr;
213
}
214

215
bool ServerContext::findAndCopy(const std::string & uri, std::u16string & content)
216
{
217
    std::lock_guard locker(_documents_mutex);
218
    auto it = _documents.find(uri);
219

220
    if (it == _documents.end())
221
        return false;
222

223
    it->second->copyContent(content);
224
    return true;
225
}
226

227
variable::JsonRpc ServerContext::initialize(lsp::ClientParams params, int id)
228
{
229
    _client_params = params;
230

231
    using namespace variable;
232

233
    if (_client_params.rootPath.empty())
234
        _client_params.rootPath = std::filesystem::current_path().string();
235
    else {
236
        std::error_code ec;
237
        std::filesystem::path work_dir = _client_params.rootPath;
238
        std::filesystem::current_path(work_dir, ec);
239
        if (ec)
240
            /// @todo Скорректировать коды (и тексты) ошибок
241
            return JsonRpc(-1, u"Unable to set work dir '" + inout::toU16(_client_params.rootPath) + u"'. " 
242
                                + inout::toU16(ec.message()), id);
243
    }
244

245
    std::vector<Value>  triggerCharacters;
246
    for (std::u16string & s : _server_capabilities.completionProvider.triggerCharacters)
247
        triggerCharacters.push_back(s);
248

249
    std::vector<Value>  tokenTypes;
250
    for (std::u16string & s : _server_capabilities.semanticTokensProvider.legend.tokenTypes)
251
        tokenTypes.push_back(s);
252

253
    std::vector<Value>  tokenModifiers;
254
    for (std::u16string & s : _server_capabilities.semanticTokensProvider.legend.tokenModifiers)
255
        tokenModifiers.push_back(s);
256

257
    std::vector<Value>  simodoRunnerOptions;
258
    for (const SimodoRunnerOption & opt : _simodo_capabilities.runnerOptions)
259
        simodoRunnerOptions.push_back(Object {{
260
            {u"languageID", opt.languageID},
261
            {u"viewName",   opt.viewName}
262
        }});
263

264
    return 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(
310
                                                std::to_string(static_cast<int>(lib_version().major()))+"."+
311
                                                std::to_string(static_cast<int>(lib_version().minor())))},
312
        }}},
313
    }}}, id);
314
}
315

316
void ServerContext::initialized()
317
{
318
    _state = ServerState::Work;
319
}
320

321
bool ServerContext::shutdown()
322
{
323
    _state = ServerState::Shutdown;
324
    /// @todo Выполнить необходимые действия для сохранения состояния, если это нужно
325
    return true;
326
}
327

328
void ServerContext::exit()
329
{
330
    _done = true;
331
}
332

333
bool ServerContext::run()
334
{
335
    bool ok = true;
336

337
    while(!is_done()) {
338
        auto [content_length, header] = readHeader(_is);
339
        if (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);
343
            ok = false;
344
            break;
345
        }
346
        {
347
            std::ostringstream so;
348
            so  << std::endl 
349
                << "RECEIVE FROM CLIENT " << std::endl
350
                << "'" << header << "'" << std::endl;
351
            _log.debug(so.str());
352
        }
353

354
        std::string content = readContent(_is, content_length);
355
        if (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);
359
            ok = false;
360
            break;
361
        }
362
        {
363
            std::ostringstream so;
364
            so << content << std::endl;
365
            _log.debug("Content of JSON-RPC", so.str());
366
        }
367

368
        if (!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);
372
            ok = false;
373
            break;
374
        }
375

376
        /// @attention Завершающую нотификацию сделал сразу при входном анализе запросов клиента, 
377
        /// чтобы корректно завершить работу
378
        if (verifyExit(content)) {
379
            _log.info("EXIT");
380
            exit();
381
            break;
382
        }
383

384
        tp().submit(new TaskDistribute(*this, content));
385

386
        if (save_mode())
387
            std::this_thread::sleep_for(std::chrono::milliseconds(500));
388
    }
389
    
390
    /// @todo Дожидаться отправки всех сообщений? Вроде бы не нужно, т.к. протокол
391
    /// исключает такую необходимость.
392
    return ok;
393
}
394

395
void ServerContext::sendResponse_loop()
396
{
397
    /// @attention Очереди входящих запросов, как и исходящих ответов 
398
    /// при завершение работы могут оставаться не пустыми.
399
    while(!_done) {
400
        variable::JsonRpc response;
401
        if (_queue_for_sending.pop_or_timeout(response, 100)) {
402
            std::string json_string = variable::toJson(response.value(), true, true);
403
            std::ostringstream log_str;
404
            log_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
}

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.