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
56void QmlCompletionSupport::setupCapabilities(QLspSpecification::ServerCapabilities &caps)
57{
58 QLspSpecification::CompletionOptions cOptions;
59 if (caps.completionProvider)
60 cOptions = *caps.completionProvider;
61 cOptions.resolveProvider = false;
62 cOptions.triggerCharacters = QList<QByteArray>({ QByteArray(".") });
63 caps.completionProvider = cOptions;
64}
65
66void QmlCompletionSupport::process(RequestPointerArgument req)
67{
68 req->sendCompletions(req->completions(m_codeModelManager, m_completionEngine));
69}
70
71QString CompletionRequest::urlAndPos() const
72{
73 return QString::fromUtf8(m_parameters.textDocument.uri) + u":"
74 + QString::number(m_parameters.position.line) + u":"
75 + QString::number(m_parameters.position.character);
76}
77
78void CompletionRequest::sendCompletions(const QList<CompletionItem> &completions)
79{
80 m_response.sendResponse(completions);
81}
82
83static bool positionIsFollowedBySpaces(qsizetype position, const QString &code)
84{
85 if (position >= code.size())
86 return false;
87
88 auto newline =
89 std::find_if(std::next(code.cbegin(), position), code.cend(),
90 [](const QChar &c) { return c == u'\n' || c == u'\r' || !c.isSpace(); });
91
92 return newline == code.cend() || newline->isSpace();
93}
94
95/*!
96\internal
97
98\note Remove this method and all its usages once the new fault-tolerant parser from QTBUG-118053 is
99introduced!!!
100
101Tries to make the document valid for the parser, to be able to provide completions after dots.
102The created DomItem is not in the qqmlcodemodel which mean it cannot be seen and cannot bother
103other modules: it would be bad to have the linting module complain about code that was modified
104here, but cannot be seen by the user.
105*/
106DomItem
107CompletionRequest::patchInvalidFileForParser(const DomItem &file, qsizetype position,
108 QmlLsp::QQmlCodeModelManager *codeModelManager) const
109{
110 const QByteArray &uri = m_parameters.textDocument.uri;
111
112 // automatic semicolon insertion after dots, if there is nothing behind the dot!
113 if (position > 0 && code[position - 1] == u'.' && positionIsFollowedBySpaces(position, code)) {
114 qCWarning(QQmlLSCompletionLog)
115 << "Patching invalid document: adding a semicolon after '.' for "
116 << QString::fromUtf8(uri);
117
118 const QString patchedCode =
119 code.first(position).append(u"_dummyIdentifier;").append(code.sliced(position));
120
121 // create a new (local) Dom only for the completions.
122 // This avoids weird behaviors, like the linting module complaining about the inserted
123 // semicolon that the user cannot see, for example.
124 DomItem newCurrent = file.environment().makeCopy(DomItem::CopyOption::EnvConnected).item();
125
126 DomItem result;
127 auto newCurrentPtr = newCurrent.ownerAs<DomEnvironment>();
128 newCurrentPtr->setLoadPaths(codeModelManager->importPathsForUrl(uri));
129 newCurrentPtr->setResourceFiles(codeModelManager->resourceFilesForFileUrl(uri));
130 newCurrentPtr->loadFile(
131 FileToLoad::fromMemory(newCurrentPtr, file.canonicalFilePath(), patchedCode),
132 [&result](Path, const DomItem &, const DomItem &newValue) {
133 result = newValue.fileObject();
134 });
135 newCurrentPtr->loadPendingDependencies();
136 return result;
137 }
138
139 qCWarning(QQmlLSCompletionLog)
140 << "No valid document for completions for " << QString::fromUtf8(uri);
141
142 return file;
143}
144
145QList<CompletionItem> CompletionRequest::completions(QmlLsp::QQmlCodeModelManager *codeModelManager,
146 const QQmlLSCompletion &completionEngine) const
147{
148 QList<CompletionItem> res;
149
150 QmlLsp::OpenDocumentSnapshot doc =
151 codeModelManager->snapshotByUrl(m_parameters.textDocument.uri);
152 const qsizetype pos = QQmlLSUtils::textOffsetFrom(code, m_parameters.position.line,
153 m_parameters.position.character);
154
155 const bool useValidDoc =
156 doc.validDoc && doc.validDocVersion && *doc.validDocVersion >= m_minVersion;
157
158 const DomItem file = useValidDoc
159 ? doc.validDoc.fileObject(QQmlJS::Dom::GoTo::MostLikely)
160 : patchInvalidFileForParser(doc.doc.fileObject(QQmlJS::Dom::GoTo::MostLikely), pos,
161 codeModelManager);
162
163 // clear reference cache to resolve latest versions (use a local env instead?)
164 if (std::shared_ptr<DomEnvironment> envPtr = file.environment().ownerAs<DomEnvironment>())
165 envPtr->clearReferenceCache();
166
167
168 CompletionContextStrings ctx(code, pos);
169 auto itemsFound = QQmlLSUtils::itemsFromTextLocation(file, m_parameters.position.line,
170 m_parameters.position.character
171 - ctx.filterChars().size());
172 if (itemsFound.isEmpty()) {
173 qCDebug(QQmlLSCompletionLog) << "No items found for completions at" << urlAndPos();
174 return {};
175 }
176
177 if (itemsFound.size() > 1) {
178 QStringList paths;
179 for (auto &it : itemsFound)
180 paths.append(it.domItem.canonicalPath().toString());
181 qCWarning(QQmlLSCompletionLog) << "Multiple elements of " << urlAndPos()
182 << " at the same depth:" << paths << "(using first)";
183 }
184 const DomItem currentItem = itemsFound.first().domItem;
185 qCDebug(QQmlLSCompletionLog) << "Completion at " << urlAndPos() << " "
186 << m_parameters.position.line << ":"
187 << m_parameters.position.character << "offset:" << pos
188 << "base:" << ctx.base() << "filter:" << ctx.filterChars()
189 << "lastVersion:" << (doc.docVersion ? (*doc.docVersion) : -1)
190 << "validVersion:"
191 << (doc.validDocVersion ? (*doc.validDocVersion) : -1) << "in"
192 << currentItem.internalKindStr() << currentItem.canonicalPath();
193 auto result = completionEngine.completions(currentItem, ctx);
194 return result;
195}
196QT_END_NAMESPACE
void registerHandlers(QLanguageServer *server, QLanguageServerProtocol *protocol) override
void setupCapabilities(QLspSpecification::ServerCapabilities &caps) override
void process(RequestPointerArgument req) override
Combined button and popup list for selecting options.
static bool positionIsFollowedBySpaces(qsizetype position, const QString &code)