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 "qqmlcompletionsupport_p.h"
5#include "qqmllsutils_p.h"
6
7#include <QtLanguageServer/private/qlanguageserverspectypes_p.h>
8#include <QtCore/qthreadpool.h>
9#include <QtCore/private/qduplicatetracker_p.h>
10#include <QtCore/QRegularExpression>
11#include <QtQmlDom/private/qqmldomexternalitems_p.h>
12#include <QtQmlDom/private/qqmldomtop_p.h>
13#include <QtQml/private/qqmlsignalnames_p.h>
14
15QT_BEGIN_NAMESPACE
16using namespace QLspSpecification;
17using namespace QQmlJS::Dom;
18using namespace Qt::StringLiterals;
19
20bool CompletionRequest::fillFrom(QmlLsp::OpenDocument doc, const Parameters &params,
21 Response &&response)
22{
23 // do not call BaseRequest::fillFrom() to avoid taking the Mutex twice and getting an
24 // inconsistent state.
25 m_parameters = params;
26 m_response = std::move(response);
27
28 if (!doc.textDocument)
29 return false;
30
31 std::optional<int> targetVersion;
32 {
33 QMutexLocker l(doc.textDocument->mutex());
34 targetVersion = doc.textDocument->version();
35 code = doc.textDocument->toPlainText();
36 }
37 m_minVersion = (targetVersion ? *targetVersion : 0);
38
39 return true;
40}
41
42QmlCompletionSupport::QmlCompletionSupport(QmlLsp::QQmlCodeModel *codeModel)
43 : BaseT(codeModel), m_completionEngine(codeModel->pluginLoader())
44{
45}
46
47void QmlCompletionSupport::registerHandlers(QLanguageServer *, QLanguageServerProtocol *protocol)
48{
49 protocol->registerCompletionRequestHandler(handler: getRequestHandler());
50 protocol->registerCompletionItemResolveRequestHandler(
51 handler: [](const QByteArray &, const CompletionItem &cParams,
52 LSPResponse<CompletionItem> &&response) { response.sendResponse(r: cParams); });
53}
54
55QString QmlCompletionSupport::name() const
56{
57 return u"QmlCompletionSupport"_s;
58}
59
60void QmlCompletionSupport::setupCapabilities(
61 const QLspSpecification::InitializeParams &,
62 QLspSpecification::InitializeResult &serverCapabilities)
63{
64 QLspSpecification::CompletionOptions cOptions;
65 if (serverCapabilities.capabilities.completionProvider)
66 cOptions = *serverCapabilities.capabilities.completionProvider;
67 cOptions.resolveProvider = false;
68 cOptions.triggerCharacters = QList<QByteArray>({ QByteArray(".") });
69 serverCapabilities.capabilities.completionProvider = cOptions;
70}
71
72void QmlCompletionSupport::process(RequestPointerArgument req)
73{
74 QmlLsp::OpenDocumentSnapshot doc =
75 m_codeModel->snapshotByUrl(url: req->m_parameters.textDocument.uri);
76 req->sendCompletions(completions: req->completions(doc, completionEngine: m_completionEngine));
77}
78
79QString CompletionRequest::urlAndPos() const
80{
81 return QString::fromUtf8(ba: m_parameters.textDocument.uri) + u":"
82 + QString::number(m_parameters.position.line) + u":"
83 + QString::number(m_parameters.position.character);
84}
85
86void CompletionRequest::sendCompletions(const QList<CompletionItem> &completions)
87{
88 m_response.sendResponse(r: completions);
89}
90
91static bool positionIsFollowedBySpaces(qsizetype position, const QString &code)
92{
93 if (position >= code.size())
94 return false;
95
96 auto newline =
97 std::find_if(first: std::next(x: code.cbegin(), n: position), last: code.cend(),
98 pred: [](const QChar &c) { return c == u'\n' || c == u'\r' || !c.isSpace(); });
99
100 return newline == code.cend() || newline->isSpace();
101}
102
103/*!
104\internal
105
106\note Remove this method and all its usages once the new fault-tolerant parser from QTBUG-118053 is
107introduced!!!
108
109Tries to make the document valid for the parser, to be able to provide completions after dots.
110The created DomItem is not in the qqmlcodemodel which mean it cannot be seen and cannot bother
111other modules: it would be bad to have the linting module complain about code that was modified
112here, but cannot be seen by the user.
113*/
114DomItem CompletionRequest::patchInvalidFileForParser(const DomItem &file, qsizetype position) const
115{
116 // automatic semicolon insertion after dots, if there is nothing behind the dot!
117 if (position > 0 && code[position - 1] == u'.' && positionIsFollowedBySpaces(position, code)) {
118 qCWarning(QQmlLSCompletionLog)
119 << "Patching invalid document: adding a semicolon after '.' for "
120 << QString::fromUtf8(ba: m_parameters.textDocument.uri);
121
122 const QString patchedCode =
123 code.first(n: position).append(v: u"_dummyIdentifier;").append(s: code.sliced(pos: position));
124
125 // create a new (local) Dom only for the completions.
126 // This avoids weird behaviors, like the linting module complaining about the inserted
127 // semicolon that the user cannot see, for example.
128 DomItem newCurrent = file.environment().makeCopy(option: DomItem::CopyOption::EnvConnected).item();
129
130 DomItem result;
131 auto newCurrentPtr = newCurrent.ownerAs<DomEnvironment>();
132 newCurrentPtr->loadFile(
133 file: FileToLoad::fromMemory(environment: newCurrentPtr, path: file.canonicalFilePath(), data: patchedCode),
134 callback: [&result](Path, const DomItem &, const DomItem &newValue) {
135 result = newValue.fileObject();
136 });
137 newCurrentPtr->loadPendingDependencies();
138 return result;
139 }
140
141 qCWarning(QQmlLSCompletionLog) << "No valid document for completions for "
142 << QString::fromUtf8(ba: m_parameters.textDocument.uri);
143
144 return file;
145}
146
147QList<CompletionItem> CompletionRequest::completions(QmlLsp::OpenDocumentSnapshot &doc,
148 const QQmlLSCompletion &completionEngine) const
149{
150 QList<CompletionItem> res;
151
152
153 const qsizetype pos = QQmlLSUtils::textOffsetFrom(code, row: m_parameters.position.line,
154 character: m_parameters.position.character);
155
156 const bool useValidDoc =
157 doc.validDoc && doc.validDocVersion && *doc.validDocVersion >= m_minVersion;
158
159 const DomItem file = useValidDoc
160 ? doc.validDoc.fileObject(option: QQmlJS::Dom::GoTo::MostLikely)
161 : patchInvalidFileForParser(file: doc.doc.fileObject(option: QQmlJS::Dom::GoTo::MostLikely), position: pos);
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, line: m_parameters.position.line,
170 character: 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(t: 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
197

Provided by KDAB

Privacy Policy
Learn to use CMake with our Intro Training
Find out more

source code of qtdeclarative/src/qmlls/qqmlcompletionsupport.cpp