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