| 1 | // Copyright (C) 2021 The Qt Company Ltd. | 
| 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only | 
| 3 |  | 
| 4 | #include "qqmllanguageserver_p.h" | 
| 5 | #include "qtextsynchronization_p.h" | 
| 6 | #include "qlanguageserver_p.h" | 
| 7 | #include "qlspcustomtypes_p.h" | 
| 8 |  | 
| 9 | #include <QtCore/qdir.h> | 
| 10 |  | 
| 11 | #include <iostream> | 
| 12 | #include <algorithm> | 
| 13 |  | 
| 14 | QT_BEGIN_NAMESPACE | 
| 15 |  | 
| 16 | namespace QmlLsp { | 
| 17 |  | 
| 18 | using namespace QLspSpecification; | 
| 19 | using namespace Qt::StringLiterals; | 
| 20 | /*! | 
| 21 | \internal | 
| 22 | \class QmlLsp::QQmlLanguageServer | 
| 23 | \brief Sets up a QmlLanguageServer. | 
| 24 |  | 
| 25 | This class sets up a QML language server. | 
| 26 |  | 
| 27 | Use the following function to send replies: | 
| 28 |  | 
| 29 | \code | 
| 30 | std::function<void(const QByteArray &)> sendData | 
| 31 | \endcode | 
| 32 |  | 
| 33 | And, feed the data that the function receives to the \c {server()->receive()} | 
| 34 | method. | 
| 35 |  | 
| 36 | Call this method only from a single thread, and do not block. To achieve this, | 
| 37 | avoid direct calls, and connect the method as a slot, while reading from another | 
| 38 | thread. | 
| 39 |  | 
| 40 | The various tasks of the language server are divided between | 
| 41 | QLanguageServerModule instances. Each instance is responsible for handling a | 
| 42 | certain subset of client requests. For example, one instance handles completion | 
| 43 | requests, another one updates the code in the code model when the client sends a | 
| 44 | new file version, and so on. The QLanguageServerModule instances are | 
| 45 | constructed and registered with QLanguageServer in the constructor of | 
| 46 | this class. | 
| 47 |  | 
| 48 | Generally, do all operations in the object thread and always call handlers from | 
| 49 | it. However, the operations can delegate the response to another thread, as the | 
| 50 | response handler is thread safe. All the methods of the \c server() object are | 
| 51 | also thread safe. | 
| 52 |  | 
| 53 | The code model starts other threads to update its state. See its documentation | 
| 54 | for more information. | 
| 55 | */ | 
| 56 | QQmlLanguageServer::QQmlLanguageServer(std::function<void(const QByteArray &)> sendData, | 
| 57 |                                        QQmlToolingSettings *settings) | 
| 58 |     : m_codeModel(nullptr, settings), | 
| 59 |       m_server(sendData), | 
| 60 |       m_textSynchronization(&m_codeModel), | 
| 61 |       m_lint(&m_server, &m_codeModel), | 
| 62 |       m_workspace(&m_codeModel), | 
| 63 |       m_completionSupport(&m_codeModel), | 
| 64 |       m_navigationSupport(&m_codeModel), | 
| 65 |       m_definitionSupport(&m_codeModel), | 
| 66 |       m_referencesSupport(&m_codeModel), | 
| 67 |       m_documentFormatting(&m_codeModel), | 
| 68 |       m_renameSupport(&m_codeModel), | 
| 69 |       m_rangeFormatting(&m_codeModel), | 
| 70 |       m_hover(&m_codeModel), | 
| 71 |       m_highlightSupport(&m_codeModel) | 
| 72 | { | 
| 73 |     m_server.addServerModule(serverModule: this); | 
| 74 |     m_server.addServerModule(serverModule: &m_textSynchronization); | 
| 75 |     m_server.addServerModule(serverModule: &m_lint); | 
| 76 |     m_server.addServerModule(serverModule: &m_workspace); | 
| 77 |     m_server.addServerModule(serverModule: &m_completionSupport); | 
| 78 |     m_server.addServerModule(serverModule: &m_navigationSupport); | 
| 79 |     m_server.addServerModule(serverModule: &m_definitionSupport); | 
| 80 |     m_server.addServerModule(serverModule: &m_referencesSupport); | 
| 81 |     m_server.addServerModule(serverModule: &m_documentFormatting); | 
| 82 |     m_server.addServerModule(serverModule: &m_renameSupport); | 
| 83 |     m_server.addServerModule(serverModule: &m_rangeFormatting); | 
| 84 |     m_server.addServerModule(serverModule: &m_hover); | 
| 85 |     m_server.addServerModule(serverModule: &m_highlightSupport); | 
| 86 |     m_server.finishSetup(); | 
| 87 |     qCWarning(lspServerLog) << "Did Setup" ; | 
| 88 | } | 
| 89 |  | 
| 90 | void QQmlLanguageServer::registerHandlers(QLanguageServer *server, | 
| 91 |                                           QLanguageServerProtocol *protocol) | 
| 92 | { | 
| 93 |     Q_UNUSED(protocol); | 
| 94 |     QObject::connect(sender: server, signal: &QLanguageServer::lifecycleError, context: this, | 
| 95 |                      slot: &QQmlLanguageServer::errorExit); | 
| 96 |     QObject::connect(sender: server, signal: &QLanguageServer::exit, context: this, slot: &QQmlLanguageServer::exit); | 
| 97 |     QObject::connect(sender: server, signal: &QLanguageServer::runStatusChanged, context: this, slot: [](QLanguageServer::RunStatus r) { | 
| 98 |         qCDebug(lspServerLog) << "runStatus"  << int(r); | 
| 99 |     }); | 
| 100 |     protocol->typedRpc()->registerNotificationHandler<Notifications::AddBuildDirsParams>( | 
| 101 |             method: QByteArray(Notifications::AddBuildDirsMethod), | 
| 102 |             handler: [this](const QByteArray &, const Notifications::AddBuildDirsParams ¶ms) { | 
| 103 |                 for (const auto &buildDirs : params.buildDirsToSet) { | 
| 104 |                     QStringList dirPaths; | 
| 105 |                     dirPaths.resize(size: buildDirs.buildDirs.size()); | 
| 106 |                     std::transform(first: buildDirs.buildDirs.begin(), last: buildDirs.buildDirs.end(), | 
| 107 |                                    result: dirPaths.begin(), unary_op: [](const QByteArray &utf8Str) { | 
| 108 |                                        return QString::fromUtf8(ba: utf8Str); | 
| 109 |                                    }); | 
| 110 |                     m_codeModel.setBuildPathsForRootUrl(url: buildDirs.baseUri, paths: dirPaths); | 
| 111 |                 } | 
| 112 |             }); | 
| 113 | } | 
| 114 |  | 
| 115 | void QQmlLanguageServer::setupCapabilities(const QLspSpecification::InitializeParams &clientInfo, | 
| 116 |                                            QLspSpecification::InitializeResult &serverInfo) | 
| 117 | { | 
| 118 |     QJsonObject expCap; | 
| 119 |     if (serverInfo.capabilities.experimental.has_value() && serverInfo.capabilities.experimental->isObject()) | 
| 120 |         expCap = serverInfo.capabilities.experimental->toObject(); | 
| 121 |     expCap.insert(key: u"addBuildDirs"_s , value: QJsonObject({ { u"supported"_s , true } })); | 
| 122 |     serverInfo.capabilities.experimental = expCap; | 
| 123 |  | 
| 124 |     if (clientInfo.workspaceFolders) { | 
| 125 |         if (auto workspaceList = | 
| 126 |                     std::get_if<QList<WorkspaceFolder>>(ptr: &*clientInfo.workspaceFolders)) { | 
| 127 |             QList<QByteArray> workspaceUris; | 
| 128 |             std::transform(first: workspaceList->cbegin(), last: workspaceList->cend(), | 
| 129 |                            result: std::back_inserter(x&: workspaceUris), | 
| 130 |                            unary_op: [](const auto &workspaceFolder) { return workspaceFolder.uri; }); | 
| 131 |             m_codeModel.setRootUrls(workspaceUris); | 
| 132 |         } | 
| 133 |     } | 
| 134 | } | 
| 135 |  | 
| 136 | QString QQmlLanguageServer::name() const | 
| 137 | { | 
| 138 |     return u"QQmlLanguageServer"_s ; | 
| 139 | } | 
| 140 |  | 
| 141 | void QQmlLanguageServer::errorExit() | 
| 142 | { | 
| 143 |     qCWarning(lspServerLog) << "Error exit" ; | 
| 144 |     fclose(stdin); | 
| 145 | } | 
| 146 |  | 
| 147 | void QQmlLanguageServer::exit() | 
| 148 | { | 
| 149 |     m_returnValue = 0; | 
| 150 |     fclose(stdin); | 
| 151 | } | 
| 152 |  | 
| 153 | int QQmlLanguageServer::returnValue() const | 
| 154 | { | 
| 155 |     return m_returnValue; | 
| 156 | } | 
| 157 |  | 
| 158 | QQmlCodeModel *QQmlLanguageServer::codeModel() | 
| 159 | { | 
| 160 |     return &m_codeModel; | 
| 161 | } | 
| 162 |  | 
| 163 | QLanguageServer *QQmlLanguageServer::server() | 
| 164 | { | 
| 165 |     return &m_server; | 
| 166 | } | 
| 167 |  | 
| 168 | TextSynchronization *QQmlLanguageServer::textSynchronization() | 
| 169 | { | 
| 170 |     return &m_textSynchronization; | 
| 171 | } | 
| 172 |  | 
| 173 | QmlLintSuggestions *QQmlLanguageServer::lint() | 
| 174 | { | 
| 175 |     return &m_lint; | 
| 176 | } | 
| 177 |  | 
| 178 | WorkspaceHandlers *QQmlLanguageServer::worspace() | 
| 179 | { | 
| 180 |     return &m_workspace; | 
| 181 | } | 
| 182 |  | 
| 183 | } // namespace QmlLsp | 
| 184 |  | 
| 185 | QT_END_NAMESPACE | 
| 186 |  |