1// SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd.
2//
3// SPDX-License-Identifier: GPL-3.0-or-later
4
5#include "client.h"
6#include "private/client_p.h"
7#include "common/util/custompaths.h"
8#include "common/util/processutil.h"
9
10#include <QMetaType>
11#include <QDebug>
12#include <QJsonArray>
13#include <QJsonObject>
14#include <QJsonDocument>
15#include <QRunnable>
16#include <QThreadPool>
17#include <QCoreApplication>
18#include <QMutex>
19#include <QMutexLocker>
20#include <QtConcurrent>
21#include <QHostAddress>
22#include <QReadWriteLock>
23
24#include <iostream>
25
26#define LANGUAGE_ADAPTER_NAME "languageadapter"
27#define LANGUAGE_ADAPTER_PATH CustomPaths::global(CustomPaths::Tools) \
28 + QDir::separator() + LANGUAGE_ADAPTER_NAME
29
30namespace newlsp {
31
32static QMutex mutex;
33class Client;
34
35Client::Client()
36 : d (new ClientPrivate(this))
37{
38 qRegisterMetaType<lsp::Diagnostics>("lsp::Diagnostics");
39 qRegisterMetaType<lsp::SemanticTokensProvider>("lsp::SemanticTokensProvider");
40 qRegisterMetaType<lsp::Symbols>("lsp::Symbols");
41 qRegisterMetaType<lsp::Locations>("lsp::Locations");
42 qRegisterMetaType<lsp::CompletionProvider>("lsp::CompletionProvider");
43 qRegisterMetaType<lsp::SignatureHelps>("lsp::SignatureHelps");
44 qRegisterMetaType<lsp::Highlights>("lsp::Highlights");
45 qRegisterMetaType<QList<lsp::Data>>("QList<lsp::Data>");
46 qRegisterMetaType<lsp::DefinitionProvider>("lsp::DefinitionProvider");
47 qRegisterMetaType<lsp::DiagnosticsParams>("lsp::DiagnosticsParams");
48 qRegisterMetaType<lsp::Data>("lsp::Data");
49 qRegisterMetaType<lsp::References>("lsp::References");
50 qRegisterMetaType<lsp::Position>("lsp::Position");
51 qRegisterMetaType<newlsp::Hover>("newlsp::Hover");
52 qRegisterMetaType<newlsp::WorkspaceEdit>("newlsp::WorkspaceEdit");
53 qRegisterMetaType<newlsp::Position>("newlsp::Position");
54 qRegisterMetaType<newlsp::Range>("newlsp::Range");
55 qRegisterMetaType<newlsp::PublishDiagnosticsParams>("newlsp::PublishDiagnosticsParams");
56 qRegisterMetaType<newlsp::DocumentColorParams>("newlsp::DocumentColorParams");
57}
58
59Client::~Client()
60{
61 if (d) {
62 delete d;
63 }
64}
65
66void Client::delta(const SemanticTokensDeltaParams &params){Q_UNUSED(params);}
67
68void Client::full(const SemanticTokensParams &params){Q_UNUSED(params);}
69
70void Client::range(const SemanticTokensRangeParams &params){Q_UNUSED(params);}
71
72void Client::didOpen(const DidOpenTextDocumentParams &params){Q_UNUSED(params);}
73
74void Client::didChange(const DidChangeTextDocumentParams &params){Q_UNUSED(params);}
75
76void Client::willSave(const WillSaveTextDocumentParams &params){Q_UNUSED(params);}
77
78void Client::willSaveWaitUntil(const WillSaveTextDocumentParams &params){Q_UNUSED(params);}
79
80void Client::didSave(const DidSaveTextDocumentParams &params){Q_UNUSED(params);}
81
82void Client::didClose(const DidCloseTextDocumentParams &params){Q_UNUSED(params);}
83
84void Client::declaration(const DeclarationParams &params){Q_UNUSED(params);}
85
86void Client::definition(const DefinitionParams &params){Q_UNUSED(params);}
87
88void Client::typeDefinition(const TypeDefinitionParams &params){Q_UNUSED(params);}
89
90void Client::implementation(const ImplementationParams &params){Q_UNUSED(params);}
91
92void Client::references(const ReferenceParams &params){Q_UNUSED(params);}
93
94void Client::prepareCallHierarchy(const CallHierarchyPrepareParams &params){Q_UNUSED(params);}
95
96void Client::prepareTypeHierarchy(const TypeHierarchyPrepareParams &params){Q_UNUSED(params);}
97
98void Client::documentHighlight(const DocumentHighlightParams &params){Q_UNUSED(params);}
99
100void Client::documentLink(const DocumentLinkParams &params){Q_UNUSED(params);}
101
102void Client::hover(const HoverParams &params){Q_UNUSED(params);}
103
104void Client::codeLens(const CodeLensParams &params){Q_UNUSED(params);}
105
106void Client::foldingRange(const FoldingRangeParams &params){Q_UNUSED(params);}
107
108void Client::selectionRange(const SelectionRangeParams &params){Q_UNUSED(params);}
109
110void Client::documentSymbol(const DocumentSymbolParams &params){Q_UNUSED(params);}
111
112void Client::inlayHint(const InlayHintParams &params){Q_UNUSED(params);}
113
114void Client::inlineValue(const InlineValueParams &params){Q_UNUSED(params);}
115
116void Client::moniker(const MonikerParams &params){Q_UNUSED(params);}
117
118void Client::completion(const CompletionParams &params){Q_UNUSED(params);}
119
120void Client::diagnostic(const DocumentDiagnosticParams &params){Q_UNUSED(params);}
121
122void Client::signatureHelp(const SignatureHelpParams &params){Q_UNUSED(params);}
123
124void Client::codeAction(const CodeActionParams &params){Q_UNUSED(params);}
125
126void Client::documentColor(const DocumentColorParams &params)
127{
128 d->callMethod("textDocument/documentColor",
129 QJsonDocument::fromJson(
130 QByteArray::fromStdString(toJsonValueStr(params))
131 ).object()
132 );
133}
134
135void Client::colorPresentation(const ColorPresentationParams &params)
136{
137 Q_UNUSED(params);
138}
139
140void Client::formatting(const DocumentFormattingParams &params)
141{
142 d->callMethod(lsp::V_TEXTDOCUMENT_FORMATTING,
143 QJsonDocument::fromJson(
144 QByteArray::fromStdString(toJsonValueStr(params)))
145 .object());
146}
147
148void Client::rangeFormatting(const DocumentRangeFormattingParams &params)
149{
150 qInfo() << QString::fromStdString(toJsonValueStr(params));
151 d->callMethod(lsp::V_TEXTDOCUMENT_RANGEFORMATTING,
152 QJsonDocument::fromJson(
153 QByteArray::fromStdString(toJsonValueStr(params)))
154 .object());
155}
156
157void Client::onTypeFormatting(const DocumentOnTypeFormattingParams &params){Q_UNUSED(params);}
158
159void Client::rename(const RenameParams &params){Q_UNUSED(params);}
160
161void Client::prepareRename(const PrepareRenameParams &params){Q_UNUSED(params);}
162
163void Client::linkedEditingRange(const LinkedEditingRangeParams &params){Q_UNUSED(params);}
164
165void Client::resolve(const CodeAction &codeAction){Q_UNUSED(codeAction);}
166
167void Client::resolve(const CompletionItem &params){Q_UNUSED(params);}
168
169void Client::resolve(){}
170
171void Client::resolve(const CodeLens &codeLens){Q_UNUSED(codeLens);}
172
173void Client::supertypes(const TypeHierarchySupertypesParams &params){Q_UNUSED(params);}
174
175void Client::subtypes(const TypeHierarchySubtypesParams &params){Q_UNUSED(params);}
176
177void Client::incomingCalls(const CallHierarchyIncomingCallsParams &params){Q_UNUSED(params);}
178
179void Client::outgoingCalls(const CallHierarchyOutgoingCallsParams &params){Q_UNUSED(params);}
180
181void Client::refresh(){}
182
183void Client::workspace_semanticTokens_refresh(){}
184
185void Client::workspace_inlayHint_refresh(){}
186
187void Client::workspace_inlineValue_refresh(){}
188
189void Client::workspace_diagnostic_refresh(){}
190
191void Client::diagnostic(const WorkspaceDiagnosticParams &params){Q_UNUSED(params);}
192
193void Client::selectLspServer(const newlsp::ProjectKey &key)
194{
195 if (d->proKey == key)
196 return;
197
198 d->proKey = key;
199 QJsonObject params = newlsp::toQJsonObject(key);
200 d->writeLspData(newlsp::notificationData(__FUNCTION__, params).toUtf8());
201}
202
203void Client::initRequest(const QString &compile)
204{
205 QString langQStr = QString::fromStdString(d->proKey.language);
206 QString workQStr = QString::fromStdString(d->proKey.workspace);
207 d->callMethod(lsp::V_INITIALIZE, lsp::initialize(workQStr, langQStr, compile));
208}
209
210void Client::openRequest(const QString &filePath)
211{
212 d->callNotification(lsp::V_TEXTDOCUMENT_DIDOPEN, lsp::didOpen(filePath));
213}
214
215void Client::closeRequest(const QString &filePath)
216{
217 d->callNotification(lsp::V_TEXTDOCUMENT_DIDCLOSE, lsp::didClose(filePath));
218}
219
220void Client::changeRequest(const QString &filePath, const QByteArray &text)
221{
222 d->callNotification(lsp::V_TEXTDOCUMENT_DIDCHANGE, lsp::didChange(filePath, text, d->fileVersion[filePath]));
223}
224
225void Client::symbolRequest(const QString &filePath)
226{
227 d->callMethod(lsp::V_TEXTDOCUMENT_DOCUMENTSYMBOL, lsp::symbol(filePath));
228}
229
230void Client::renameRequest(const QString &filePath, const lsp::Position &pos, const QString &newName)
231{
232 d->callMethod(lsp::V_TEXTDOCUMENT_RENAME, lsp::rename(filePath, pos, newName));
233}
234
235void Client::definitionRequest(const QString &filePath, const lsp::Position &pos)
236{
237 d->callMethod(lsp::V_TEXTDOCUMENT_DEFINITION, lsp::definition(filePath, pos));
238}
239
240void Client::completionRequest(const QString &filePath, const lsp::Position &pos)
241{
242 d->callMethod(lsp::V_TEXTDOCUMENT_COMPLETION, lsp::completion(filePath, pos));
243}
244
245void Client::signatureHelpRequest(const QString &filePath, const lsp::Position &pos)
246{
247 d->callMethod(lsp::V_TEXTDOCUMENT_SIGNATUREHELP, lsp::signatureHelp(filePath, pos));
248}
249
250void Client::referencesRequest(const QString &filePath, const lsp::Position &pos)
251{
252 d->callMethod(lsp::V_TEXTDOCUMENT_REFERENCES, lsp::references(filePath, pos));
253}
254
255void Client::docHighlightRequest(const QString &filePath, const lsp::Position &pos)
256{
257 d->callMethod(lsp::V_TEXTDOCUMENT_DOCUMENTHIGHLIGHT, lsp::documentHighlight(filePath, pos));
258}
259
260void Client::docSemanticTokensFull(const QString &filePath)
261{
262 d->callMethod(lsp::V_TEXTDOCUMENT_SEMANTICTOKENS_FULL, lsp::documentSemanticTokensFull(filePath));
263}
264
265void Client::docHoverRequest(const QString &filePath, const lsp::Position &pos)
266{
267 d->callMethod(lsp::V_TEXTDOCUMENT_HOVER, lsp::hover(filePath, pos));
268}
269
270void Client::shutdownRequest()
271{
272 d->callMethod(lsp::V_SHUTDOWN, lsp::shutdown());
273}
274
275void Client::exitRequest()
276{
277 d->callMethod(lsp::V_EXIT, lsp::exit());
278}
279
280void ClientPrivate::callMethod(const QString &method, const QJsonObject &params)
281{
282 requestIndex ++;
283 requestSave.insert(requestIndex, method);
284 writeLspData(newlsp::methodData(requestIndex, method, params).toUtf8());
285}
286
287void ClientPrivate::callNotification(const QString &method, const QJsonObject &params)
288{
289 writeLspData(newlsp::notificationData(method, params).toUtf8());
290}
291
292void ClientPrivate::writeLspData(const QByteArray &jsonObj)
293{
294 qInfo() << "\nclient->server:\n{\n" << jsonObj << "\n}";
295 q->write(jsonObj);
296 q->waitForBytesWritten();
297}
298
299bool ClientPrivate::calledError(const QJsonObject &jsonObj)
300{
301 if (jsonObj.keys().contains(lsp::K_ERROR)) {
302 QString errStr = "Failed, called error. code ";
303 auto errorObj = jsonObj.value(lsp::K_ERROR).toObject();
304 auto calledID = jsonObj.value(K_ID).toInt();
305 errStr += QString("%0 ").arg(errorObj.value(lsp::K_CODE).toInt());
306 errStr += QString(",%0 ").arg(errorObj.value(lsp::K_MESSAGE).toString());
307 if (requestSave.keys().contains(calledID)) {
308 auto requestMethod = requestSave.value(calledID);
309 errStr += QString("from: %0").arg(requestMethod);
310 }
311 requestSave.remove(calledID);
312 return true;
313 }
314 return false;
315}
316
317bool ClientPrivate::initResult(const QJsonObject &jsonObj)
318{
319 auto calledID = jsonObj.value(K_ID).toInt();
320 if (requestSave.keys().contains(calledID)
321 && requestSave.value(calledID) == lsp::V_INITIALIZE) {
322 requestSave.remove(calledID);
323
324 QJsonObject semanticTokensProviderObj = jsonObj.value("result").toObject()
325 .value("capabilities").toObject()
326 .value("semanticTokensProvider").toObject();
327 QJsonObject fullObj = semanticTokensProviderObj.value("full").toObject();
328 QJsonObject legendObj = semanticTokensProviderObj.value("legend").toObject();
329
330 lsp::SemanticTokensProvider provider
331 {
332 lsp::SemanticTokensProvider::Full
333 {
334 fullObj.value("delta").toBool()
335 },
336 lsp::SemanticTokensProvider::Legend
337 {
338 cvtStringList(legendObj.value("tokenTypes").toArray()),
339 cvtStringList(legendObj.value("tokenModifiers").toArray())
340 },
341 semanticTokensProviderObj.value("range").toBool()
342 };
343
344 secTokensProvider = provider;
345
346 emit q->requestResult(provider);
347 return true;
348 }
349 return false;
350}
351
352bool ClientPrivate::openResult(const QJsonObject &jsonObj)
353{
354 auto calledID = jsonObj.value(K_ID).toInt();
355 if (requestSave.keys().contains(calledID)
356 && requestSave.value(calledID) == lsp::V_TEXTDOCUMENT_DIDOPEN) {
357 requestSave.remove(calledID);
358 return true;
359 }
360 return false;
361}
362
363bool ClientPrivate::changeResult(const QJsonObject &jsonObj)
364{
365 auto calledID = jsonObj.value(K_ID).toInt();
366 if (requestSave.keys().contains(calledID)
367 && requestSave.value(calledID) == lsp::V_TEXTDOCUMENT_DIDCHANGE) {
368 requestSave.remove(calledID);
369 return true;
370 }
371 return false;
372}
373
374bool ClientPrivate::symbolResult(const QJsonObject &jsonObj)
375{
376 auto calledID = jsonObj.value(K_ID).toInt();
377 if (requestSave.keys().contains(calledID)
378 && requestSave.value(calledID) == lsp::V_TEXTDOCUMENT_DOCUMENTSYMBOL) {
379 requestSave.remove(calledID);
380 return true;
381 }
382 return false;
383}
384
385bool ClientPrivate::renameResult(const QJsonObject &jsonObj)
386{
387 auto calledID = jsonObj.value(K_ID).toInt();
388 if (requestSave.keys().contains(calledID)
389 && requestSave.value(calledID) == lsp::V_TEXTDOCUMENT_RENAME) {
390 requestSave.remove(calledID);
391 QJsonObject resultObj = jsonObj.value(K_RESULT).toObject();
392
393 QJsonObject changesObj = resultObj.value("changes").toObject();
394 newlsp::WorkspaceEdit workspaceEdit;
395 if (!changesObj.isEmpty()) {
396 // std::optional<> changes;
397 newlsp::WorkspaceEdit::Changes changes;
398 for (auto fileKey : changesObj.keys()) {
399 auto addionTextEditArray = changesObj[fileKey].toArray();
400 std::vector<newlsp::TextEdit> textEdits;
401 for (auto addion : addionTextEditArray){
402 auto addionObj = addion.toObject();
403 auto rangeObj = addionObj.value(lsp::K_RANGE).toObject();
404 auto startObj = rangeObj.value(lsp::K_START).toObject();
405 auto endObj = rangeObj.value(lsp::K_END).toObject();
406 std::string newText = addionObj.value(lsp::K_NewText).toString().toStdString();
407 newlsp::Position startPos{startObj.value(lsp::K_LINE).toInt(), startObj.value(lsp::K_CHARACTER).toInt()};
408 newlsp::Position endPos{endObj.value(lsp::K_LINE).toInt(), endObj.value(lsp::K_CHARACTER).toInt()};
409 newlsp::Range range{startPos, endPos};
410 textEdits.push_back(newlsp::TextEdit{range, newText});
411 }
412 changes[fileKey.toStdString()] = textEdits;
413 }
414 workspaceEdit.changes = changes;
415 }
416
417 QJsonArray documentChangesArray = resultObj.value("documentChanges").toArray();
418 if (!documentChangesArray.isEmpty()) {
419 newlsp::WorkspaceEdit::DocumentChanges documentChanges;
420 std::vector<newlsp::TextDocumentEdit> textDocumentEdits;
421 std::vector<newlsp::CreateFile> createFiles;
422 std::vector<newlsp::RenameFile> renameFiles;
423 std::vector<newlsp::DeleteFile> deleteFiles;
424 for (auto one : documentChangesArray) {
425 QJsonObject oneObj = one.toObject();
426 if (oneObj.contains("edits") && oneObj.contains("textDocument")) { // std::vector<newlsp::TextDocumentEdit>
427 newlsp::TextDocumentEdit textDocumentEdit;
428
429 newlsp::OptionalVersionedTextDocumentIdentifier ovtdi;
430 QJsonObject textDocumentOneObj = oneObj.value("textDocument").toObject();
431 ovtdi.version = textDocumentOneObj.value(lsp::K_VERSION).toInt();
432 ovtdi.uri = textDocumentOneObj.value(lsp::K_URI).toString().toStdString();
433 textDocumentEdit.textDocument.version = ovtdi.version;
434 textDocumentEdit.textDocument.uri = ovtdi.uri;
435
436 std::vector<newlsp::AnnotatedTextEdit> annotatedTextEdits;
437 std::vector<newlsp::TextEdit> textEdits;
438 QJsonArray editsArray = oneObj.value("edits").toArray();
439 for (auto editsOne : editsArray) {
440 QJsonObject editsOneObj = editsOne.toObject();
441 QJsonObject editsOneRangeObj = editsOneObj.value("range").toObject();
442 QJsonObject editsOneRangeStartObj = editsOneRangeObj.value(lsp::K_START).toObject();
443 QJsonObject editsOneRangeEndObj = editsOneRangeObj.value(lsp::K_END).toObject();
444 std::string editsOneNewText = editsOneObj.value("newText").toString().toStdString();
445 newlsp::Range editsOneRange{
446 newlsp::Position{editsOneRangeStartObj.value(lsp::K_LINE).toInt(), editsOneRangeStartObj.value(lsp::K_CHARACTER).toInt()},
447 newlsp::Position{editsOneRangeEndObj.value(lsp::K_LINE).toInt(), editsOneRangeEndObj.value(lsp::K_CHARACTER).toInt()}
448 };
449 if (editsOneObj.contains("annotationId")) { // edits: (TextEdit | AnnotatedTextEdit)[];
450 newlsp::ChangeAnnotationIdentifier changeAnnIdf = editsOneObj.value("annotationId").toString().toStdString();
451 newlsp::AnnotatedTextEdit annotatedTextEdit{};
452 annotatedTextEdit.range = editsOneRange;
453 annotatedTextEdit.newText = editsOneNewText;
454 annotatedTextEdit.annotationId = changeAnnIdf;
455 annotatedTextEdits.push_back(annotatedTextEdit);
456 } else {
457 newlsp::TextEdit textEdit;
458 textEdit.range = editsOneRange;
459 textEdit.newText = editsOneNewText;
460 textEdits.push_back(textEdit);
461 }
462 }
463 if (!annotatedTextEdits.empty()) {
464 textDocumentEdit.edits = annotatedTextEdits;
465 } else if (!textEdits.empty()){
466 textDocumentEdit.edits = textEdits;
467 }
468 textDocumentEdits.push_back(textDocumentEdit);
469 } else {
470 QString oneObjKind = oneObj.value("kind").toString();
471 if ("create" == oneObjKind) {
472 newlsp::CreateFile createFile;
473 createFile.uri = oneObj.value("uri").toString().toStdString();
474 QJsonObject oneObjOptions = oneObj.value("options").toObject();
475 if (!oneObjOptions.empty()) {
476 newlsp::CreateFileOptions options;
477 options.overwrite = oneObjOptions.value("overwrite").toBool();
478 options.ignoreIfExists = oneObjOptions.value("ignoreIfExists").toBool();
479 createFile.options = options;
480 }
481 QJsonValue annotationIdJV = oneObj.value("annotationId");
482 if (!annotationIdJV.isNull()) {
483 createFile.annotationId = annotationIdJV.toString().toStdString();
484 }
485 createFiles.push_back(createFile);
486 } else if ("delete" == oneObjKind) {
487 newlsp::DeleteFile deleteFile;
488 deleteFile.uri = oneObj.value("uri").toString().toStdString();
489 QJsonObject oneObjOptions = oneObj.value("options").toObject();
490 if (!oneObjOptions.empty()) {
491 newlsp::DeleteFileOptions options;
492 options.recursive = oneObjOptions.value("recursive").toBool();
493 options.ignoreIfNotExists = oneObjOptions.value("ignoreIfNotExists").toBool();
494 deleteFile.options = options;
495 }
496 QJsonValue annotationIdJV = oneObj.value("annotationId");
497 if (!annotationIdJV.isNull()) {
498 deleteFile.annotationId = annotationIdJV.toString().toStdString();
499 }
500 deleteFiles.push_back(deleteFile);
501 } else if ("rename" == oneObjKind) {
502 newlsp::RenameFile renameFile;
503 renameFile.oldUri = oneObj.value("oldUri").toString().toStdString();
504 renameFile.newUri = oneObj.value("newUri").toString().toStdString();
505 QJsonObject oneObjOptions = oneObj.value("options").toObject();
506 if (!oneObjOptions.empty()) {
507 newlsp::RenameFileOptions options;
508 options.overwrite = oneObjOptions.value("overwrite").toBool();
509 options.ignoreIfExists = oneObjOptions.value("ignoreIfExists").toBool();
510 renameFile.options = options;
511 }
512 QJsonValue annotationIdJV = oneObj.value("annotationId");
513 if (!annotationIdJV.isNull()) {
514 renameFile.annotationId = annotationIdJV.toString().toStdString();
515 }
516 renameFiles.push_back(renameFile);
517 }
518 }
519 // set workspaceEdit.documentChanges
520 if (!textDocumentEdits.empty()) {
521 workspaceEdit.documentChanges = textDocumentEdits;
522 } else if (!createFiles.empty()) {
523 workspaceEdit.documentChanges = createFiles;
524 } else if (!renameFiles.empty()) {
525 workspaceEdit.documentChanges = renameFiles;
526 } else if (!deleteFiles.empty()) {
527 workspaceEdit.documentChanges = deleteFiles;
528 }
529 }
530 }
531
532 QJsonObject changeAnnotationsObj = resultObj.value("changeAnnotations").toObject();
533 if (!changeAnnotationsObj.isEmpty()) {
534 newlsp::WorkspaceEdit::ChangeAnnotations changeAnnotations;
535 for (auto idKey: changeAnnotationsObj.keys()) {
536 QJsonObject changeAnnotationObj = changeAnnotationsObj[idKey].toObject();
537 newlsp::ChangeAnnotation changeAnnotation;
538 std::string label = changeAnnotationObj.value("label").toString().toStdString();
539 changeAnnotation.label = label;
540 if (changeAnnotationObj.contains("needsConfirmation")) {
541 changeAnnotation.needsConfirmation = changeAnnotationObj.value("needsConfirmation").toBool();
542 }
543 if (changeAnnotationObj.contains("description")) {
544 changeAnnotation.description = changeAnnotationObj.value("description").toString().toStdString();
545 }
546 changeAnnotations[idKey.toStdString()] = changeAnnotation;
547 }
548 workspaceEdit.changeAnnotations = changeAnnotations;
549 }
550 emit q->renameRes(workspaceEdit);
551 return true;
552 }
553 return false;
554}
555
556bool ClientPrivate::rangeFormattingResult(const QJsonObject &jsonObj)
557{
558 auto calledID = jsonObj.value(K_ID).toInt();
559 if (requestSave.keys().contains(calledID)
560 && requestSave.value(calledID) == lsp::V_TEXTDOCUMENT_RANGEFORMATTING) {
561 requestSave.remove(calledID);
562
563 QJsonValue resultJV = jsonObj.value(K_RESULT);
564 if (resultJV.isArray()) {
565 QJsonArray resultArray = resultJV.toArray();
566 if (resultArray.count() <= 0) {
567 return false;
568 }
569 std::vector<TextEdit> edits;
570 for (auto one : resultArray) {
571 QJsonObject oneObj = one.toObject();
572 TextEdit edit;
573 edit.newText = oneObj.value(lsp::K_NewText).toString().toStdString();
574 QJsonObject rangeObj = oneObj.value(lsp::K_RANGE).toObject();
575 QJsonObject startObj = rangeObj.value(lsp::K_START).toObject();
576 QJsonObject endObj = rangeObj.value(lsp::K_END).toObject();
577 edit.range.start = {startObj.value(lsp::K_LINE).toInt(),
578 startObj.value(lsp::K_CHARACTER).toInt()};
579 edit.range.end = {endObj.value(lsp::K_LINE).toInt(),
580 endObj.value(lsp::K_CHARACTER).toInt()};
581 edits.push_back(edit);
582 }
583 emit q->rangeFormattingRes(edits);
584 }
585 }
586 return false;
587}
588
589bool ClientPrivate::definitionResult(const QJsonObject &jsonObj)
590{
591 auto calledID = jsonObj.value(K_ID).toInt();
592 if (requestSave.values().contains(lsp::V_TEXTDOCUMENT_DEFINITION)
593 && requestSave.value(calledID) == lsp::V_TEXTDOCUMENT_DEFINITION) {
594 requestSave.remove(calledID);
595
596 QJsonValue resultJV = jsonObj.value(K_RESULT);
597 if (resultJV.isArray()) {
598 QJsonArray resultArray = resultJV.toArray();
599 if (resultArray.count() <= 0){
600 return false;
601 }
602 std::vector<newlsp::Location> locations;
603 std::vector<newlsp::LocationLink> locationLinks;
604 for (auto one : resultArray) {
605 QJsonObject oneObj = one.toObject();
606 if (oneObj.contains("range") && oneObj.contains("uri")) {
607 QJsonObject rangeObj = oneObj.value("range").toObject();
608 QJsonObject startObj = rangeObj.value("start").toObject();
609 QJsonObject endObj = rangeObj.value("end").toObject();
610 std::string uri = oneObj.value("uri").toString().toStdString();
611 newlsp::Range range{
612 {startObj.value("line").toInt(), startObj.value("character").toInt()},
613 {endObj.value("line").toInt(), endObj.value("character").toInt()}
614 };
615 locations.push_back({uri, range});
616 } else if (oneObj.contains("originSelectionRange")
617 && oneObj.contains("targetUri")
618 && oneObj.contains("targetRange")
619 && oneObj.contains("targetSelectionRange")) {
620 std::string targetUri = oneObj.value("targetUri").toString().toStdString();
621 QJsonObject rangeObj, startObj, endObj;
622 // originSelectionRange
623 rangeObj = oneObj.value("originSelectionRange").toObject();
624 startObj = rangeObj.value("start").toObject();
625 endObj = endObj.value("end").toObject();
626 newlsp::Range originSelectionRange {
627 {startObj.value("line").toInt(), startObj.value("character").toInt()},
628 {endObj.value("line").toInt(), endObj.value("character").toInt()}
629 };
630 // targetRange
631 rangeObj = oneObj.value("targetRange").toObject();
632 startObj = rangeObj.value("start").toObject();
633 endObj = endObj.value("end").toObject();
634 newlsp::Range targetRange {
635 {startObj.value("line").toInt(), startObj.value("character").toInt()},
636 {endObj.value("line").toInt(), endObj.value("character").toInt()}
637 };
638 // targetSelectionRange
639 rangeObj = oneObj.value("targetSelectionRange").toObject();
640 startObj = rangeObj.value("start").toObject();
641 endObj = endObj.value("end").toObject();
642 newlsp::Range targetSelectionRange {
643 {startObj.value("line").toInt(), startObj.value("character").toInt()},
644 {endObj.value("line").toInt(), endObj.value("character").toInt()}
645 };
646 locationLinks.push_back({originSelectionRange, targetUri, targetRange, targetSelectionRange});
647 }
648 }
649
650 if (!locationLinks.empty()) {
651 emit q->definitionRes(locationLinks);
652 return true;
653 } else if (!locations.empty()) {
654 emit q->definitionRes(locations);
655 return true;
656 }
657 } else if (resultJV.isObject()){
658 QJsonObject locationObj = resultJV.toObject();
659 if (locationObj.contains("range") && locationObj.contains("uri")) {
660 QJsonObject rangeObj = locationObj.value("range").toObject();
661 QJsonObject startObj = rangeObj.value("start").toObject();
662 QJsonObject endObj = rangeObj.value("end").toObject();
663 std::string uri = locationObj.value("uri").toString().toStdString();
664 newlsp::Range range{
665 {startObj.value("line").toInt(), startObj.value("character").toInt()},
666 {endObj.value("line").toInt(), endObj.value("character").toInt()}
667 };
668 emit q->definitionRes(newlsp::Location{uri, range});
669 return true;
670 }
671 }
672 }
673 return false;
674}
675
676bool ClientPrivate::completionResult(const QJsonObject &jsonObj)
677{
678 auto calledID = jsonObj.value(K_ID).toInt();
679 if(requestSave.values().contains(lsp::V_TEXTDOCUMENT_COMPLETION)
680 && requestSave.value(calledID) == lsp::V_TEXTDOCUMENT_COMPLETION) {
681 requestSave.remove(calledID);
682 QJsonObject resultObj = jsonObj.value(K_RESULT).toObject();
683 QJsonArray itemsArray = resultObj.value("items").toArray();
684 lsp::CompletionProvider completionProvider;
685 lsp::CompletionItems items;
686 for (auto item : itemsArray) {
687 QJsonObject itemObj = item.toObject();
688 QJsonArray editsArray = itemObj.value("additionalTextEdits").toArray();
689 lsp::AdditionalTextEdits additionalTextEdits;
690 for (auto edit : editsArray) {
691 QJsonObject textEditObj = edit.toObject();
692 QString newText = textEditObj.value("newText").toString();
693 QJsonObject rangeObj = textEditObj.value("range").toObject();
694 QJsonObject startObj = rangeObj.value(lsp::K_START).toObject();
695 QJsonObject endObj = rangeObj.value(lsp::K_END).toObject();
696 lsp::Position start{startObj.value(lsp::K_LINE).toInt(), startObj.value(lsp::K_CHARACTER).toInt()};
697 lsp::Position end{endObj.value(lsp::K_LINE).toInt(), endObj.value(lsp::K_CHARACTER).toInt()};
698 additionalTextEdits << lsp::TextEdit{ newText, lsp::Range{ start, end} };
699 }
700
701 QJsonObject documentationObj = itemObj.value("documentation").toObject();
702 struct lsp::Documentation documentation {
703 documentationObj.value("kind").toString(), documentationObj.value("value").toString()
704 };
705
706 QJsonObject textEditObj = itemObj.value("textEdit").toObject();
707 QJsonObject textEditRangeObj = textEditObj.value("range").toObject();
708 QJsonObject textEditStartObj = textEditRangeObj.value(lsp::K_START).toObject();
709 QJsonObject textEditEndObj = textEditRangeObj.value(lsp::K_END).toObject();
710 QString newText = textEditObj.value("newText").toString();
711 lsp::Position start{textEditStartObj.value(lsp::K_LINE).toInt(), textEditStartObj.value(lsp::K_CHARACTER).toInt()};
712 lsp::Position end{textEditEndObj.value(lsp::K_LINE).toInt(), textEditEndObj.value(lsp::K_CHARACTER).toInt()};
713 lsp::TextEdit textEdit{newText, lsp::Range{start, end} };
714
715 items << lsp::CompletionItem {
716 additionalTextEdits,
717 documentation,
718 itemObj.value("filterText").toString(),
719 itemObj.value("insertText").toString(),
720 (lsp::InsertTextFormat)itemObj.value("insertTextFormat").toInt(),
721 (lsp::CompletionItem::Kind)(itemObj.value("kind").toInt()),
722 itemObj.value("label").toString(),
723 itemObj.value("score").toDouble(),
724 itemObj.value("sortText").toString(),
725 textEdit };
726 }
727
728 completionProvider.items = items;
729 completionProvider.isIncomplete = resultObj.value("isIncomplete").toBool();
730
731 emit q->requestResult(completionProvider);
732 return true;
733 }
734 return false;
735}
736
737bool ClientPrivate::signatureHelpResult(const QJsonObject &jsonObj)
738{
739 auto calledID = jsonObj.value(K_ID).toInt();
740 if(requestSave.keys().contains(calledID)
741 && requestSave.value(calledID) == lsp::V_TEXTDOCUMENT_SIGNATUREHELP) {
742 requestSave.remove(calledID);
743 return true;
744 }
745 return false;
746}
747
748bool ClientPrivate::hoverResult(const QJsonObject &jsonObj)
749{
750 auto calledID = jsonObj.value(K_ID).toInt();
751 if(requestSave.keys().contains(calledID)
752 && requestSave.value(calledID) == lsp::V_TEXTDOCUMENT_HOVER) {
753 requestSave.remove(calledID);
754 QJsonObject resultObj = jsonObj.value("result").toObject();
755 newlsp::Hover hover;
756 QJsonValue contentsJV = resultObj.value("contents");
757 if (contentsJV.isArray()) { // to MarkedString[]
758 QJsonArray contentsJA = contentsJV.toArray();
759 std::vector<newlsp::MarkedString> markedStringVec{};
760 for (auto one : contentsJA) {
761 if (one.isObject()) {
762 QJsonObject oneObj = one.toObject();
763 markedStringVec.push_back(newlsp::MarkedString{
764 oneObj.value("language").toString().toStdString(),
765 oneObj.value("value").toString().toStdString()
766 });
767 } else if (one.isString()){
768 markedStringVec.push_back(newlsp::MarkedString{one.toString().toStdString()});
769 }
770 }
771 hover.contents = markedStringVec;
772 } else { // MarkedString or MarkupContent
773 QJsonObject contentsObj = contentsJV.toObject();
774 QJsonValue contents_kind_JV = contentsObj.value("kind");
775 QJsonValue contents_value_JV = contentsObj.value("value");
776 QJsonValue contents_language_JV = contentsObj.value("language");
777 if (!contents_kind_JV.isNull() && !contents_value_JV.isNull()) { // MarkupContent
778 hover.contents = newlsp::MarkupContent {
779 contents_kind_JV.toString().toStdString(),
780 contents_value_JV.toString().toStdString() };
781 } else if (!contents_language_JV.isNull() && !contents_value_JV.isNull()) { // MarkupString
782 hover.contents = newlsp::MarkedString {
783 contents_language_JV.toString().toStdString(),
784 contents_value_JV.toString().toStdString() };
785 } else {
786 hover.contents = newlsp::MarkedString { contentsJV.toString().toStdString() };
787 }
788 }
789 QJsonValue rangeJV = resultObj.value("range");
790 if (!rangeJV.isNull()) {
791 QJsonObject rangeObj = rangeJV.toObject();
792 QJsonObject startObj = rangeObj.value(lsp::K_START).toObject();
793 QJsonObject endObj = rangeObj.value(lsp::K_END).toObject();
794 hover.range = newlsp::Range{
795 newlsp::Position { startObj.value(lsp::K_LINE).toInt(), startObj.value(lsp::K_CHARACTER).toInt() },
796 newlsp::Position { endObj.value(lsp::K_LINE).toInt(), endObj.value(lsp::K_CHARACTER).toInt() } };
797 }
798 emit q->hoverRes(hover);
799 return true;
800 }
801 return false;
802}
803
804bool ClientPrivate::referencesResult(const QJsonObject &jsonObj)
805{
806 auto calledID = jsonObj.value(K_ID).toInt();
807 if(requestSave.keys().contains(calledID)
808 && requestSave.value(calledID) == lsp::V_TEXTDOCUMENT_REFERENCES) {
809 lsp::References refs;
810 auto resultArray = jsonObj.value(K_RESULT).toArray();
811 for (auto item : resultArray) {
812 auto itemObj = item.toObject();
813 auto rangeObj = itemObj.value(lsp::K_RANGE).toObject();
814 auto startObj = rangeObj.value(lsp::K_START).toObject();
815 auto endObj = rangeObj.value(lsp::K_END).toObject();
816 QString url = itemObj.value(lsp::K_URI).toString();
817 lsp::Location location;
818 location.fileUrl = url;
819 location.range.start = lsp::Position{startObj.value(lsp::K_LINE).toInt(),
820 startObj.value(lsp::K_CHARACTER).toInt()};
821 location.range.end = lsp::Position{endObj.value(lsp::K_LINE).toInt(),
822 endObj.value(lsp::K_CHARACTER).toInt()};
823 refs << location;
824 }
825 emit q->requestResult(refs);
826 requestSave.remove(calledID);
827 return true;
828 }
829 return false;
830}
831
832bool ClientPrivate::docHighlightResult(const QJsonObject &jsonObj)
833{
834 auto calledID = jsonObj.value(K_ID).toInt();
835 if(requestSave.keys().contains(calledID)
836 && requestSave.value(calledID) == lsp::V_TEXTDOCUMENT_DOCUMENTHIGHLIGHT) {
837 requestSave.remove(calledID);
838 return true;
839 }
840 return false;
841}
842
843bool ClientPrivate::docSemanticTokensFullResult(const QJsonObject &jsonObj)
844{
845 auto calledID = jsonObj.value(K_ID).toInt();
846 if(requestSave.keys().contains(calledID)
847 && requestSave.value(calledID) == lsp::V_TEXTDOCUMENT_SEMANTICTOKENS + "/full") {
848 requestSave.remove(calledID);
849
850 QJsonObject result = jsonObj.value(K_RESULT).toObject();
851 semanticTokenResultId = jsonObj.value("resultId").toInt();
852
853 QJsonArray dataArray = result.value(lsp::K_DATA).toArray();
854 if(dataArray.isEmpty())
855 return true;
856
857 QList<lsp::Data> results;
858 auto itera = dataArray.begin();
859 while (itera != dataArray.end()) {
860 results << lsp::Data {
861 lsp::Position{itera++->toInt(), itera++->toInt()},
862 int(itera++->toInt()),
863 itera++->toInt(),
864 lsp::fromTokenModifiers(itera++->toInt())};
865 }
866 emit q->requestResult(results);
867 return true;
868 }
869 return false;
870}
871
872bool ClientPrivate::closeResult(const QJsonObject &jsonObj)
873{
874 auto calledID = jsonObj.value(K_ID).toInt();
875 if(requestSave.keys().contains(calledID)
876 && requestSave.value(calledID) == lsp::V_TEXTDOCUMENT_DIDCLOSE) {
877 requestSave.remove(calledID);
878 return true;
879 }
880 return false;
881}
882
883bool ClientPrivate::exitResult(const QJsonObject &jsonObj)
884{
885 auto calledID = jsonObj.value(K_ID).toInt();
886 if(requestSave.keys().contains(calledID)
887 && requestSave.value(calledID) == lsp::V_EXIT) {
888 requestSave.remove(calledID);
889 return true;
890 }
891 return false;
892}
893
894bool ClientPrivate::shutdownResult(const QJsonObject &jsonObj)
895{
896 auto calledID = jsonObj.value(K_ID).toInt();
897 if(requestSave.keys().contains(calledID)
898 && requestSave.value(calledID) == lsp::V_SHUTDOWN) {
899 requestSave.remove(calledID);
900 return true;
901 }
902 return false;
903}
904
905bool ClientPrivate::diagnosticsCalled(const QJsonObject &jsonObj)
906{
907 if (!jsonObj.keys().contains(K_METHOD)
908 || jsonObj.value(K_METHOD).toString() != lsp::V_TEXTDOCUMENT_PUBLISHDIAGNOSTICS)
909 return false;
910
911 newlsp::PublishDiagnosticsParams publishDiagnosticsParams;
912
913 QJsonObject paramsObj = jsonObj.value(K_PARAMS).toObject();
914 QJsonArray array = paramsObj.value(lsp::K_DIAGNOSTICS).toArray();
915
916 for (auto val : array) {
917 QJsonObject diagnosticObj = val.toObject();
918 QJsonObject rangeObj = diagnosticObj.value(lsp::K_RANGE).toObject();
919 QJsonObject startObj = rangeObj.value(lsp::K_START).toObject();
920 QJsonObject endObj = rangeObj.value(lsp::K_END).toObject();
921 std::vector<newlsp::DiagnosticRelatedInformation> reletedInformation;
922 for (auto reInfo : diagnosticObj.value(lsp::K_RELATEDINFOMATION).toArray()) {
923 auto reInfoObj = reInfo.toObject();
924 QJsonObject reInfoLocationObj = reInfoObj.value(lsp::K_LOCATION).toObject();
925 QJsonObject reInfoLocationRangeObj = reInfoLocationObj.value(lsp::K_RANGE).toObject();
926 QJsonObject reInfoLocationStartObj = reInfoLocationRangeObj.value(lsp::K_START).toObject();
927 QJsonObject reInfoLocationEndObj = reInfoLocationRangeObj.value(lsp::K_END).toObject();
928 std::string reInfoLocationUrl = reInfoLocationObj.value(lsp::K_URI).toString().toStdString();
929 std::string reInfoMessage = reInfoObj.value(lsp::K_MESSAGE).toString().toStdString();
930 newlsp::DiagnosticRelatedInformation infomationOne
931 {
932 newlsp::Location {
933 newlsp::DocumentUri { reInfoLocationUrl },
934 newlsp::Range {
935 { reInfoLocationRangeObj.value(lsp::K_LINE).toInt(), reInfoLocationRangeObj.value(lsp::K_CHARACTER).toInt()},
936 { reInfoLocationEndObj.value(lsp::K_LINE).toInt(), reInfoLocationEndObj.value(lsp::K_CHARACTER).toInt()}
937 }
938 },
939 std::string{
940 reInfoMessage
941 }
942 };
943 reletedInformation.push_back(infomationOne);
944 }
945
946 newlsp::Diagnostic diagnostic;
947 diagnostic.range = {
948 { startObj.value(lsp::K_LINE).toInt(), startObj.value(lsp::K_CHARACTER).toInt()},
949 { endObj.value(lsp::K_LINE).toInt(), endObj.value(lsp::K_CHARACTER).toInt()}
950 };
951
952 QJsonValue severityJV = diagnosticObj.value(lsp::K_SEVERITY);
953 if (!severityJV.isNull()) {
954 diagnostic.severity = lsp::Diagnostic::Severity(severityJV.toInt());
955 }
956
957 QJsonValue codeJV = diagnosticObj.value(lsp::K_CODE);
958 if (!codeJV.isNull()) {
959 if (codeJV.isString()) {
960 diagnostic.code = codeJV.toString().toStdString();
961 } else {
962 diagnostic.code = int(codeJV.toInt());
963 }
964 }
965
966 QJsonValue codeDescriptionJV = diagnosticObj.value("codeDescription");
967 if (!codeDescriptionJV.isNull()) {
968 QJsonObject cdObj = codeDescriptionJV.toObject();
969 diagnostic.codeDescription = {cdObj.value("href").toString().toStdString()};
970 }
971
972 QJsonValue sourceJV = diagnosticObj.value("source");
973 if (!sourceJV.isNull()) {
974 diagnostic.source = sourceJV.toString().toStdString();
975 }
976
977 QJsonValue messageJV = diagnosticObj.value(lsp::K_MESSAGE);
978 if (!messageJV.isNull()) {
979 diagnostic.message = messageJV.toString().toStdString();
980 }
981
982 QJsonValue tagsJV = diagnosticObj.value("tags");
983 if (!tagsJV.isNull()) {
984 std::vector<Enum::DiagnosticTag::type_value> tags;
985 if (tagsJV.isArray()) {
986 QJsonArray tagsArray = tagsJV.toArray();
987 for (auto one : tagsArray) {
988 tags.push_back(one.toInt());
989 }
990 }
991 diagnostic.tags = tags;
992 }
993
994 if (!reletedInformation.empty()) {
995 diagnostic.relatedInformation = reletedInformation;
996 }
997
998 QJsonValue dataJV = diagnosticObj.value("data");
999 if (!dataJV.isNull()) {
1000 //nothing to do
1001 }
1002 publishDiagnosticsParams.diagnostics.push_back(diagnostic);
1003 }
1004
1005 publishDiagnosticsParams.version = paramsObj.value(lsp::K_VERSION).toInt();
1006 publishDiagnosticsParams.uri = paramsObj.value(lsp::K_URI).toString().toStdString();
1007 emit q->publishDiagnostics(publishDiagnosticsParams);
1008 return true;
1009}
1010
1011bool ClientPrivate::serverCalled(const QJsonObject &jsonObj)
1012{
1013 if (diagnosticsCalled(jsonObj))
1014 return true;
1015
1016 return false;
1017}
1018
1019bool ClientPrivate::calledResult(const QJsonObject &jsonObj)
1020{
1021 int calledID = jsonObj.value(K_ID).toInt();
1022 if (!requestSave.keys().contains(calledID)
1023 || !jsonObj.contains(K_RESULT))
1024 return false;
1025
1026 bool any = false;
1027 any |= initResult(jsonObj);
1028 any |= openResult(jsonObj);
1029 any |= symbolResult(jsonObj);
1030 any |= definitionResult(jsonObj);
1031 any |= referencesResult(jsonObj);
1032 any |= renameResult(jsonObj);
1033 any |= completionResult(jsonObj);
1034 any |= signatureHelpResult(jsonObj);
1035 any |= hoverResult(jsonObj);
1036 any |= docHighlightResult(jsonObj);
1037 any |= docSemanticTokensFullResult(jsonObj);
1038 any |= closeResult(jsonObj);
1039 any |= shutdownResult(jsonObj);
1040 any |= exitResult(jsonObj);
1041 any |= rangeFormattingResult(jsonObj);
1042
1043 requestSave.remove(calledID);
1044
1045 return any;
1046}
1047
1048void ClientPrivate::doReadStdoutLine()
1049{
1050 while (q->canReadLine()) {
1051 doReadedLine(q->readLine());
1052 }
1053 if (q->bytesAvailable()) {
1054 doReadedLine(q->readAll());
1055 }
1056}
1057
1058void ClientPrivate::identifyJsonObject(const QJsonObject &jsonObj)
1059{
1060 if (calledError(jsonObj)) {
1061 qWarning() << "\nclient <- server:\n{\n" << jsonObj << "\n}";
1062 return;
1063 }
1064
1065
1066 if (calledResult(jsonObj)) {
1067 qInfo() << "\nclient <- server:\n{\n" << jsonObj << "\n}";
1068 return;
1069 }
1070
1071 if (serverCalled(jsonObj)) {
1072 qInfo() << "\nclient <- server:\n{\n" << jsonObj << "\n}";
1073 return;
1074 }
1075}
1076
1077lsp::SemanticTokensProvider Client::initSecTokensProvider()
1078{
1079 return d->secTokensProvider;
1080}
1081
1082ClientPrivate::ClientPrivate(Client * const q)
1083 : newlsp::StdoutJsonRpcParser()
1084 , q (q)
1085 , requestIndex (0)
1086 , requestSave ({})
1087 , semanticTokenResultId (0)
1088 , fileVersion ({})
1089 , secTokensProvider ({})
1090 , proKey ({})
1091{
1092
1093 q->setProgram(LANGUAGE_ADAPTER_PATH);
1094 q->setArguments({"--parentPid", QString::number(qApp->applicationPid())});
1095
1096 QObject::connect(q, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished),
1097 this, [&](int exitCode, QProcess::ExitStatus status)
1098 {
1099 qCritical() << "\nclient <- server\n{\n" << "lspServerProc finished:\n" << exitCode << status << "\n}";
1100 });
1101
1102 QObject::connect(q, &QProcess::readyReadStandardError,
1103 this, [=]()
1104 {
1105 qCritical() << "\nclient <- server\n{\n" <<q->readAllStandardError() << "\n}";
1106 });
1107
1108 QObject::connect(q, &QProcess::readyReadStandardOutput,
1109 this, &ClientPrivate::doReadStdoutLine,
1110 Qt::DirectConnection);
1111
1112 if (q->state() == QProcess::NotRunning) {
1113 q->start();
1114 q->waitForStarted();
1115 }
1116
1117 QObject::connect(this, &StdoutJsonRpcParser::readedJsonObject,
1118 this, &ClientPrivate::identifyJsonObject);
1119}
1120
1121QStringList ClientPrivate::cvtStringList(const QJsonArray &array)
1122{
1123 QStringList ret;
1124 for (auto val : array) {
1125 ret << val.toString();
1126 }
1127 return ret;
1128}
1129} // namespace newlsp
1130
1131
1132