Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qqmlcompletionsupport.cpp
Go to the documentation of this file.
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// Qt-Security score:significant reason:default
4
7
8#include <QtLanguageServer/private/qlanguageserverspectypes_p.h>
9#include <QtCore/qthreadpool.h>
10#include <QtCore/private/qduplicatetracker_p.h>
11#include <QtCore/QRegularExpression>
12#include <QtQmlDom/private/qqmldomexternalitems_p.h>
13#include <QtQmlDom/private/qqmldomtop_p.h>
14#include <QtQml/private/qqmlsignalnames_p.h>
15
17using namespace QLspSpecification;
18using namespace QQmlJS::Dom;
19using namespace Qt::StringLiterals;
20
21bool CompletionRequest::fillFrom(QmlLsp::OpenDocument doc, const Parameters &params,
22 Response &&response)
23{
24 // do not call BaseRequest::fillFrom() to avoid taking the Mutex twice and getting an
25 // inconsistent state.
26 m_parameters = params;
27 m_response = std::move(response);
28
29 if (!doc.textDocument)
30 return false;
31
32 std::optional<int> targetVersion;
33 {
34 QMutexLocker l(doc.textDocument->mutex());
35 targetVersion = doc.textDocument->version();
36 code = doc.textDocument->toPlainText();
37 }
38 m_minVersion = (targetVersion ? *targetVersion : 0);
39
40 return true;
41}
42
43QmlCompletionSupport::QmlCompletionSupport(QmlLsp::QQmlCodeModelManager *codeModelManager)
44 : BaseT(codeModelManager), m_completionEngine(codeModelManager->pluginLoader())
45{
46}
47
48void QmlCompletionSupport::registerHandlers(QLanguageServer *, QLanguageServerProtocol *protocol)
49{
50 protocol->registerCompletionRequestHandler(getRequestHandler());
51 protocol->registerCompletionItemResolveRequestHandler(
52 [](const QByteArray &, const CompletionItem &cParams,
53 LSPResponse<CompletionItem> &&response) { response.sendResponse(cParams); });
54}
55
57{
58 return u"QmlCompletionSupport"_s;
59}
60
62 const QLspSpecification::InitializeParams &,
63 QLspSpecification::InitializeResult &serverCapabilities)
64{
65 QLspSpecification::CompletionOptions cOptions;
66 if (serverCapabilities.capabilities.completionProvider)
67 cOptions = *serverCapabilities.capabilities.completionProvider;
68 cOptions.resolveProvider = false;
69 cOptions.triggerCharacters = QList<QByteArray>({ QByteArray(".") });
70 serverCapabilities.capabilities.completionProvider = cOptions;
71}
72
73void QmlCompletionSupport::process(RequestPointerArgument req)
74{
76 m_codeModelManager->snapshotByUrl(req->m_parameters.textDocument.uri);
77 req->sendCompletions(req->completions(doc, m_completionEngine));
78}
79
80QString CompletionRequest::urlAndPos() const
81{
82 return QString::fromUtf8(m_parameters.textDocument.uri) + u":"
83 + QString::number(m_parameters.position.line) + u":"
84 + QString::number(m_parameters.position.character);
85}
86
87void CompletionRequest::sendCompletions(const QList<CompletionItem> &completions)
88{
89 m_response.sendResponse(completions);
90}
91
92static bool positionIsFollowedBySpaces(qsizetype position, const QString &code)
93{
94 if (position >= code.size())
95 return false;
96
97 auto newline =
98 std::find_if(std::next(code.cbegin(), position), code.cend(),
99 [](const QChar &c) { return c == u'\n' || c == u'\r' || !c.isSpace(); });
100
101 return newline == code.cend() || newline->isSpace();
102}
103
104/*!
105\internal
106
107\note Remove this method and all its usages once the new fault-tolerant parser from QTBUG-118053 is
108introduced!!!
109
110Tries to make the document valid for the parser, to be able to provide completions after dots.
111The created DomItem is not in the qqmlcodemodel which mean it cannot be seen and cannot bother
112other modules: it would be bad to have the linting module complain about code that was modified
113here, but cannot be seen by the user.
114*/
115DomItem CompletionRequest::patchInvalidFileForParser(const DomItem &file, qsizetype position) const
116{
117 // automatic semicolon insertion after dots, if there is nothing behind the dot!
118 if (position > 0 && code[position - 1] == u'.' && positionIsFollowedBySpaces(position, code)) {
119 qCWarning(QQmlLSCompletionLog)
120 << "Patching invalid document: adding a semicolon after '.' for "
121 << QString::fromUtf8(m_parameters.textDocument.uri);
122
123 const QString patchedCode =
124 code.first(position).append(u"_dummyIdentifier;").append(code.sliced(position));
125
126 // create a new (local) Dom only for the completions.
127 // This avoids weird behaviors, like the linting module complaining about the inserted
128 // semicolon that the user cannot see, for example.
129 DomItem newCurrent = file.environment().makeCopy(DomItem::CopyOption::EnvConnected).item();
130
131 DomItem result;
132 auto newCurrentPtr = newCurrent.ownerAs<DomEnvironment>();
133 newCurrentPtr->loadFile(
134 FileToLoad::fromMemory(newCurrentPtr, file.canonicalFilePath(), patchedCode),
135 [&result](Path, const DomItem &, const DomItem &newValue) {
136 result = newValue.fileObject();
137 });
138 newCurrentPtr->loadPendingDependencies();
139 return result;
140 }
141
142 qCWarning(QQmlLSCompletionLog) << "No valid document for completions for "
143 << QString::fromUtf8(m_parameters.textDocument.uri);
144
145 return file;
146}
147
148QList<CompletionItem> CompletionRequest::completions(QmlLsp::OpenDocumentSnapshot &doc,
149 const QQmlLSCompletion &completionEngine) const
150{
151 QList<CompletionItem> res;
152
153
154 const qsizetype pos = QQmlLSUtils::textOffsetFrom(code, m_parameters.position.line,
155 m_parameters.position.character);
156
157 const bool useValidDoc =
158 doc.validDoc && doc.validDocVersion && *doc.validDocVersion >= m_minVersion;
159
160 const DomItem file = useValidDoc
161 ? doc.validDoc.fileObject(QQmlJS::Dom::GoTo::MostLikely)
162 : patchInvalidFileForParser(doc.doc.fileObject(QQmlJS::Dom::GoTo::MostLikely), pos);
163
164 // clear reference cache to resolve latest versions (use a local env instead?)
165 if (std::shared_ptr<DomEnvironment> envPtr = file.environment().ownerAs<DomEnvironment>())
166 envPtr->clearReferenceCache();
167
168
169 CompletionContextStrings ctx(code, pos);
170 auto itemsFound = QQmlLSUtils::itemsFromTextLocation(file, m_parameters.position.line,
171 m_parameters.position.character
172 - ctx.filterChars().size());
173 if (itemsFound.isEmpty()) {
174 qCDebug(QQmlLSCompletionLog) << "No items found for completions at" << urlAndPos();
175 return {};
176 }
177
178 if (itemsFound.size() > 1) {
179 QStringList paths;
180 for (auto &it : itemsFound)
181 paths.append(it.domItem.canonicalPath().toString());
182 qCWarning(QQmlLSCompletionLog) << "Multiple elements of " << urlAndPos()
183 << " at the same depth:" << paths << "(using first)";
184 }
185 const DomItem currentItem = itemsFound.first().domItem;
186 qCDebug(QQmlLSCompletionLog) << "Completion at " << urlAndPos() << " "
187 << m_parameters.position.line << ":"
188 << m_parameters.position.character << "offset:" << pos
189 << "base:" << ctx.base() << "filter:" << ctx.filterChars()
190 << "lastVersion:" << (doc.docVersion ? (*doc.docVersion) : -1)
191 << "validVersion:"
192 << (doc.validDocVersion ? (*doc.validDocVersion) : -1) << "in"
193 << currentItem.internalKindStr() << currentItem.canonicalPath();
194 auto result = completionEngine.completions(currentItem, ctx);
195 return result;
196}
197QT_END_NAMESPACE
Implements a server for the language server protocol.
QString name() const override
void registerHandlers(QLanguageServer *server, QLanguageServerProtocol *protocol) override
void setupCapabilities(const QLspSpecification::InitializeParams &clientInfo, QLspSpecification::InitializeResult &) override
void process(RequestPointerArgument req) override
static bool positionIsFollowedBySpaces(qsizetype position, const QString &code)