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
qqmlcodemodelmanager.cpp
Go to the documentation of this file.
1// Copyright (C) 2025 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:trusted-sources
4
7
8#include <memory>
9
10QT_BEGIN_NAMESPACE
11
12namespace QmlLsp {
13
14using namespace QQmlJS::Dom;
15using namespace Qt::StringLiterals;
16
17void QQmlCodeModelManager::onCMakeProberFinished(int exitCode, QProcess::ExitStatus exitStatus)
18{
19 if (m_cmakeStatus == DoesNotHaveCMake)
20 return;
21
22 if (exitStatus != QProcess::NormalExit || exitCode != 0) {
23 disableCMakeCalls();
24 return;
25 }
26 m_cmakeStatus = HasCMake;
27 for (const auto &ws : m_workspaces)
28 ws.codeModel->tryEnableCMakeCalls(&m_processScheduler);
29}
30
31/*!
32\internal
33Enable and initialize the functionality that uses CMake, if CMake exists.
34
35\note Set the buildpaths before calling this method!
36*/
37void QQmlCodeModelManager::tryEnableCMakeCalls()
38{
39 m_cmakeStatus = IsProbingCMake;
40
41 m_cmakeProber.setProgram(u"cmake"_s);
42 m_cmakeProber.setArguments({ u"--version"_s });
43 QObject::connect(&m_cmakeProber, &QProcess::finished, this,
44 &QQmlCodeModelManager::onCMakeProberFinished);
45 QObject::connect(&m_cmakeProber, &QProcess::errorOccurred, this,
46 &QQmlCodeModelManager::disableCMakeCalls);
47
48 m_cmakeProber.start();
49}
50
51QQmlCodeModelManager::QQmlCodeModelManager(QObject *parent, QQmlToolingSharedSettings *settings)
52 : QObject{ parent }, m_settings(settings), m_pluginLoader(QmlLSPluginInterface_iid, u"/qmlls"_s)
53{
54 const QByteArray defaultCodeModel;
55 appendWorkspace(defaultCodeModel, ManagedByServer);
56}
57
58QQmlCodeModelManager::~QQmlCodeModelManager()
59{
60 m_cmakeProber.kill();
61 m_cmakeProber.waitForFinished();
62}
63
64QQmlCodeModelManager::WorkspaceIterator
65QQmlCodeModelManager::findWorkspaceForFile(const QByteArray &url)
66{
67 Q_ASSERT(!m_workspaces.empty());
68 // if file was already opened before: re-use same CodeModel as last time
69 if (auto it = m_file2CodeModel.find(url); it != m_file2CodeModel.end()) {
70 const auto result = findWorkspace(it->second);
71 Q_ASSERT(result != m_workspaces.end());
72 return result;
73 }
74
75 long longestRootUrl = 0;
76 WorkspaceIterator result = m_workspaces.begin();
77 for (auto it = m_workspaces.begin(), end = m_workspaces.end(); it != end; ++it) {
78 if (it->toBeClosed)
79 continue;
80 const QByteArray rootUrl = it->url;
81 if (!url.startsWith(rootUrl))
82 continue;
83
84 if (rootUrl.size() == url.size())
85 return it;
86
87 const long rootUrlLength = rootUrl.length();
88 if (rootUrlLength > longestRootUrl) {
89 longestRootUrl = rootUrlLength;
90 result = it;
91 }
92 }
93
94 // check .qmlls.build.ini for a potentially better match
95 if (const ModuleSetting moduleSetting =
96 m_buildInformation.settingFor(QUrl::fromEncoded(url).toLocalFile());
97 !moduleSetting.importPaths.isEmpty()) {
98 const QByteArray rootUrl = QUrl::fromLocalFile(moduleSetting.sourceFolder).toEncoded();
99 if (longestRootUrl < rootUrl.size()) {
100 appendWorkspace(rootUrl, ManagedByServer);
101 return --m_workspaces.end();
102 }
103 }
104 Q_ASSERT(result != m_workspaces.end());
105 return result;
106}
107
108QQmlCodeModel *QQmlCodeModelManager::findCodeModelForFile(const QByteArray &url)
109{
110 return findWorkspaceForFile(url)->codeModel.get();
111}
112
113QQmlCodeModelManager::WorkspaceMutableIterator
114QQmlCodeModelManager::findWorkspace(const QByteArray &url)
115{
116 return std::find_if(m_workspaces.begin(), m_workspaces.end(),
117 [&url](const QQmlWorkspace &ws) { return ws.url == url; });
118}
119
120void QQmlCodeModelManager::appendWorkspace(const QByteArray &url, ManagedBy managedBy)
121{
122 QQmlWorkspace ws;
123 ws.url = url;
124 ws.codeModel = std::make_unique<QQmlCodeModel>(url, this, m_settings);
125
126 // the non-fallback codemodel inherits the default values from the fallback codemodel
127 if (!url.isEmpty()) {
128 ws.codeModel->setDocumentationRootPath(defaultDocumentationRootPath());
129 ws.codeModel->setBuildPaths(defaultBuildPaths());
130 ws.codeModel->setImportPaths(
131 m_buildInformation.importPathsFor(QUrl::fromEncoded(url).toLocalFile())
132 + defaultImportPaths());
133 }
134
135 QObject::connect(ws.codeModel.get(), &QQmlCodeModel::updatedSnapshot, this,
136 &QQmlCodeModelManager::updatedSnapshot);
137 ws.managedByClient = managedBy == ManagedByClient;
138
139 switch (m_cmakeStatus) {
140 case DoesNotHaveCMake:
141 ws.codeModel->disableCMakeCalls();
142 break;
143 case HasCMake:
144 ws.codeModel->tryEnableCMakeCalls(&m_processScheduler);
145 break;
146 case IsProbingCMake:
147 // will be enabled once the CMake probing process finishes
148 break;
149 }
150 m_workspaces.emplace_back(std::move(ws));
151}
152
153QQmlCodeModelManager::WorkspaceIterator
154QQmlCodeModelManager::workspaceFromBuildFolder(const QString &fileName,
155 const QStringList &buildFolders)
156{
157 m_buildInformation.loadSettingsFrom(buildFolders);
158 const ModuleSetting setting = m_buildInformation.settingFor(fileName);
159 QByteArray url = QUrl::fromLocalFile(setting.sourceFolder).toEncoded();
160 if (auto it = findWorkspace(url); it != m_workspaces.end())
161 return it;
162 appendWorkspace(url, ManagedByServer);
163 return --m_workspaces.end();
164}
165
166void QQmlCodeModelManager::disableCMakeCalls()
167{
168 m_cmakeStatus = DoesNotHaveCMake;
169 for (const auto &ws : m_workspaces)
170 ws.codeModel->disableCMakeCalls();
171}
172
173OpenDocumentSnapshot QQmlCodeModelManager::snapshotByUrl(const QByteArray &url)
174{
175 return findCodeModelForFile(url)->snapshotByUrl(url);
176}
177
178void QQmlCodeModelManager::removeDirectory(const QByteArray &url)
179{
180 findCodeModelForFile(url)->removeDirectory(url);
181}
182
183void QQmlCodeModelManager::newOpenFile(const QByteArray &url, int version, const QString &docText)
184{
185 const auto ws = findWorkspaceForFile(url);
186 m_file2CodeModel[url] = ws->url;
187 ws->codeModel->newOpenFile(url, version, docText);
188}
189
190OpenDocument QQmlCodeModelManager::openDocumentByUrl(const QByteArray &url)
191{
192 return findCodeModelForFile(url)->openDocumentByUrl(url);
193}
194
195RegisteredSemanticTokens &QQmlCodeModelManager::registeredTokens(const QByteArray &url)
196{
197 return findCodeModelForFile(url)->registeredTokens();
198}
199
200void QQmlCodeModelManager::closeOpenFile(const QByteArray &url)
201{
202 m_file2CodeModel.erase(url);
203 const auto it = findWorkspaceForFile(url);
204 it->codeModel->closeOpenFile(url);
205
206 // don't close the default workspace
207 if (it->url.isEmpty())
208 return;
209
210 // close empty WS when managed by server or when client marked ws as toBeClosed.
211 if ((it->managedByClient && it->toBeClosed) || !it->managedByClient) {
212 if (it->codeModel->isEmpty())
213 m_workspaces.erase(it);
214 }
215}
216
217QList<QByteArray> QQmlCodeModelManager::rootUrls() const
218{
219 QList<QByteArray> result;
220 result.reserve(m_workspaces.size());
221 for (const QQmlWorkspace &ws : m_workspaces) {
222 result << ws.url;
223 }
224 return result;
225}
226
227void QQmlCodeModelManager::addRootUrls(const QList<QByteArray> &urls)
228{
229 for (const QByteArray &url : urls) {
230 if (const auto it = findWorkspace(url); it != m_workspaces.end()) {
231 it->toBeClosed = false;
232 continue;
233 }
234
235 appendWorkspace(url, ManagedByClient);
236 }
237}
238
239void QQmlCodeModelManager::removeRootUrls(const QList<QByteArray> &urls)
240{
241 for (const QByteArray &url : urls) {
242 if (auto it = findWorkspace(url); it != m_workspaces.end() && it->managedByClient)
243 it->toBeClosed = true;
244 }
245}
246
247QStringList QQmlCodeModelManager::importPathsForUrl(const QByteArray &url)
248{
249 return findCodeModelForFile(url)->importPathsForUrl(url);
250}
251
252QStringList QQmlCodeModelManager::buildPathsForFileUrl(const QByteArray &url)
253{
254 return findCodeModelForFile(url)->buildPathsForFileUrl(url);
255}
256
257QByteArray QQmlCodeModelManager::shortestRootUrlForFile(const QByteArray &fileUrl) const
258{
259 // fallback value for candidate is the empty url of the default workspace
260 QByteArray candidate;
261
262 // ignore the default workspace which is at the front of m_workspaces
263 Q_ASSERT(m_workspaces.size() > 0);
264 Q_ASSERT(m_workspaces.front().url.isEmpty());
265 auto it = std::find_if(
266 ++m_workspaces.cbegin(), m_workspaces.cend(),
267 [&fileUrl](const QQmlWorkspace &ws) { return fileUrl.startsWith(ws.url); });
268
269 if (it != m_workspaces.cend())
270 candidate = it->url;
271
272 for (; it != m_workspaces.cend(); ++it) {
273 if (it->url.length() < candidate.length() && fileUrl.startsWith(it->url))
274 candidate = it->url;
275 }
276 return candidate;
277}
278
279void QQmlCodeModelManager::setDocumentationRootPath(const QString &path)
280{
281 // Note: this function can only be called after the fallback workspace was created but before
282 // all other potential workspaces are created, for example when setting the import paths set via
283 // commandline option or environment variable.
284 Q_ASSERT(m_workspaces.size() == 1);
285 for (const auto &ws : m_workspaces)
286 ws.codeModel->setDocumentationRootPath(path);
287}
288
289void QQmlCodeModelManager::setVerbose(bool verbose)
290{
291 m_verbose = verbose;
292 for (const auto &ws : m_workspaces)
293 ws.codeModel->setVerbose(verbose);
294}
295
296void QQmlCodeModelManager::setBuildPathsForRootUrl(const QByteArray &url, const QStringList &paths)
297{
298 const QStringList defaultPaths = defaultBuildPaths();
299 auto setBuildPaths = [&paths, &defaultPaths, this](const QQmlWorkspace &ws) {
300 ws.codeModel->setBuildPaths(paths + defaultPaths);
301
302 if (const QStringList importPaths =
303 m_buildInformation.importPathsFor(QUrl::fromEncoded(ws.url).toLocalFile());
304 !importPaths.isEmpty()) {
305 ws.codeModel->setImportPaths(importPaths + defaultImportPaths());
306 }
307 };
308
309 m_buildInformation.loadSettingsFrom(paths);
310
311 // build paths passed by -b have an empty url and apply to all workspaces
312 if (url.isEmpty()) {
313 for (QQmlWorkspace &ws : m_workspaces)
314 setBuildPaths(ws);
315 return;
316 }
317
318 const auto ws = findWorkspaceForFile(url);
319 setBuildPaths(*ws);
320}
321
322void QQmlCodeModelManager::addOpenToUpdate(const QByteArray &url)
323{
324 findCodeModelForFile(url)->addOpenToUpdate(url, NormalUpdate);
325}
326
327void QQmlCodeModelManager::setImportPaths(const QStringList &paths)
328{
329 // Note: this function can only be called after the fallback workspace was created but before
330 // all other potential workspaces are created, for example when setting the import paths set via
331 // commandline option or environment variable.
332 Q_ASSERT(m_workspaces.size() == 1);
333 for (const auto &ws : m_workspaces)
334 ws.codeModel->setImportPaths(paths);
335}
336
337HelpManager *QQmlCodeModelManager::helpManagerForUrl(const QByteArray &url)
338{
339 return findCodeModelForFile(url)->helpManager();
340}
341
342} // namespace QmlLsp
343
344QT_END_NAMESPACE
#define QmlLSPluginInterface_iid