Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Toggle main menu visibility
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
5
#
include
"qqmlcodemodelmanager_p.h"
6
#
include
"qqmllsplugin_p.h"
7
8
#
include
<
QtQmlCompiler
/
private
/
qqmljsutils_p
.
h
>
9
10
#
include
<
memory
>
11
12
QT_BEGIN_NAMESPACE
13
14
namespace
QmlLsp {
15
16
Q_STATIC_LOGGING_CATEGORY(lcCodeModelManager,
"qt.languageserver.codemodelmanager"
)
17
18
using
namespace
QQmlJS::Dom;
19
using
namespace
Qt::StringLiterals;
20
21
void
QQmlCodeModelManager::onCMakeProberFinished(
int
exitCode, QProcess::ExitStatus exitStatus)
22
{
23
if
(m_cmakeStatus == DoesNotHaveCMake)
24
return
;
25
26
if
(exitStatus != QProcess::NormalExit || exitCode != 0) {
27
disableCMakeCalls();
28
return
;
29
}
30
m_cmakeStatus = HasCMake;
31
for
(
const
auto
&ws : m_workspaces)
32
ws.codeModel->tryEnableCMakeCalls(&m_processScheduler);
33
}
34
35
/*!
36
\internal
37
Enable and initialize the functionality that uses CMake, if CMake exists.
38
39
\note Set the buildpaths before calling this method!
40
*/
41
void
QQmlCodeModelManager::tryEnableCMakeCalls()
42
{
43
m_cmakeStatus = IsProbingCMake;
44
45
m_cmakeProber.setProgram(u"cmake"_s);
46
m_cmakeProber.setArguments({ u"--version"_s });
47
QObject::connect(&m_cmakeProber, &QProcess::finished,
this
,
48
&QQmlCodeModelManager::onCMakeProberFinished);
49
QObject::connect(&m_cmakeProber, &QProcess::errorOccurred,
this
,
50
&QQmlCodeModelManager::disableCMakeCalls);
51
52
m_cmakeProber.start();
53
}
54
55
QQmlCodeModelManager::QQmlCodeModelManager(QObject *parent, QQmlToolingSharedSettings *settings)
56
: QObject{ parent }, m_settings(settings), m_pluginLoader(
QmlLSPluginInterface_iid
, u"/qmlls"_s)
57
{
58
const
QByteArray defaultCodeModel;
59
appendWorkspace(defaultCodeModel, ManagedByServer);
60
connect(&m_processScheduler, &QProcessScheduler::done,
this
,
61
&QQmlCodeModelManager::onBuildFinished);
62
connect(&m_processScheduler, &QProcessScheduler::started,
this
,
63
&QQmlCodeModelManager::backgroundBuildStarted);
64
connect(&m_processScheduler, &QProcessScheduler::cancelled,
this
,
65
&QQmlCodeModelManager::backgroundBuildCancelled);
66
}
67
68
void
QQmlCodeModelManager::onBuildFinished(
const
QByteArray &url)
69
{
70
qCInfo(lcCodeModelManager) <<
"Build for workspace"
<< url <<
"finished."
;
71
auto
it = findWorkspace(url);
72
if
(it == fallbackWorkspace() || it == m_workspaces.end()) {
73
emit backgroundBuildFinished(url);
74
return
;
75
}
76
77
// refresh information obtained from build folder, like .qmlls.build.ini or resource files
78
const
QStringList buildPaths = it->codeModel->buildPaths();
79
m_buildInformation.loadSettingsFrom(buildPaths, ForceUpdate);
80
setBuildPathsOn(&*it, buildPaths, AppendPathsFromFallback);
81
it->codeModel->reloadAllOpenFiles();
82
emit backgroundBuildFinished(url);
83
emit configurationChanged();
84
}
85
86
void
QQmlCodeModelManager::prepareForShutdown()
87
{
88
for
(
auto
it = m_workspaces.begin(), end = m_workspaces.end(); it != end; ++it)
89
it->codeModel->prepareForShutdown();
90
}
91
92
QQmlCodeModelManager::~QQmlCodeModelManager()
93
{
94
m_cmakeProber.kill();
95
m_cmakeProber.waitForFinished();
96
prepareForShutdown();
97
}
98
99
QQmlCodeModelManager::WorkspaceIterator
100
QQmlCodeModelManager::findWorkspaceForFile(
const
QByteArray &url)
101
{
102
Q_ASSERT(!m_workspaces.empty());
103
// if file was already opened before: re-use same CodeModel as last time
104
if
(
auto
it = m_file2CodeModel.find(url); it != m_file2CodeModel.end()) {
105
const
auto
result = findWorkspace(it->second);
106
Q_ASSERT(result != m_workspaces.end());
107
return
result;
108
}
109
110
long
longestRootUrl = 0;
111
WorkspaceIterator result = m_workspaces.begin();
112
for
(
auto
it = m_workspaces.begin(), end = m_workspaces.end(); it != end; ++it) {
113
if
(it->toBeClosed)
114
continue
;
115
const
QByteArray rootUrl = it->url;
116
if
(!url.startsWith(rootUrl))
117
continue
;
118
119
if
(rootUrl.size() == url.size())
120
return
it;
121
122
const
long
rootUrlLength = rootUrl.length();
123
if
(rootUrlLength > longestRootUrl) {
124
longestRootUrl = rootUrlLength;
125
result = it;
126
}
127
}
128
129
// check .qmlls.build.ini for a potentially better match
130
if
(
const
ModuleSetting moduleSetting =
131
m_buildInformation.settingFor(QUrl::fromEncoded(url).toLocalFile());
132
!moduleSetting.importPaths.isEmpty()) {
133
const
QByteArray rootUrl = QUrl::fromLocalFile(moduleSetting.sourceFolder).toEncoded();
134
if
(longestRootUrl < rootUrl.size()) {
135
appendWorkspace(rootUrl, ManagedByServer);
136
return
--m_workspaces.end();
137
}
138
}
139
Q_ASSERT(result != m_workspaces.end());
140
return
result;
141
}
142
143
QQmlCodeModel *QQmlCodeModelManager::findCodeModelForFile(
const
QByteArray &url)
144
{
145
return
findWorkspaceForFile(url)->codeModel.get();
146
}
147
148
QQmlCodeModelManager::WorkspaceMutableIterator
149
QQmlCodeModelManager::findWorkspace(
const
QByteArray &url)
150
{
151
return
std::find_if(m_workspaces.begin(), m_workspaces.end(),
152
[&url](
const
QQmlWorkspace &ws) {
return
ws.url == url; });
153
}
154
155
void
QQmlCodeModelManager::setBuildPathsOn(
const
QQmlWorkspace *ws,
const
QStringList &buildFolder,
156
SetBuildPathOption option)
157
{
158
ws->codeModel->setBuildPaths(
159
buildFolder
160
+ (option == DontAppendPathsFromFallback ? QStringList{} : defaultBuildPaths()));
161
162
const
QString file = QUrl::fromEncoded(ws->url).toLocalFile();
163
ws->codeModel->setImportPaths(
164
m_buildInformation.importPathsFor(file)
165
+ (option == DontAppendPathsFromFallback ? QStringList{} : defaultImportPaths()));
166
167
if
(
const
QStringList resourceFiles = m_buildInformation.resourceFilesFor(file);
168
!resourceFiles.isEmpty()) {
169
ws->codeModel->setResourceFiles(resourceFiles);
170
return
;
171
}
172
// fallback for qt projects < 6.11 without resource files in their .qmlls.build.ini
173
const
QStringList resourceFiles =
174
QQmlJSUtils::resourceFilesFromBuildFolders(ws->codeModel->buildPaths());
175
ws->codeModel->setResourceFiles(
176
resourceFiles
177
+ (option == DontAppendPathsFromFallback ? QStringList{} : defaultResourceFiles()));
178
}
179
180
void
QQmlCodeModelManager::appendWorkspace(
const
QByteArray &url, ManagedBy managedBy)
181
{
182
QQmlWorkspace ws;
183
ws.url = url;
184
ws.codeModel = std::make_unique<QQmlCodeModel>(url,
this
, m_settings);
185
186
// the non-fallback codemodel inherits the default values from the fallback codemodel
187
if
(!url.isEmpty()) {
188
ws.codeModel->setCMakeJobs(defaultCMakeJobs());
189
ws.codeModel->setDocumentationRootPath(defaultDocumentationRootPath());
190
191
setBuildPathsOn(&ws, {}, AppendPathsFromFallback);
192
}
193
194
QObject::connect(ws.codeModel.get(), &QQmlCodeModel::updatedSnapshot,
this
,
195
&QQmlCodeModelManager::updatedSnapshot);
196
ws.managedByClient = managedBy == ManagedByClient;
197
198
switch
(m_cmakeStatus) {
199
case
DoesNotHaveCMake:
200
ws.codeModel->disableCMakeCalls();
201
break
;
202
case
HasCMake:
203
ws.codeModel->tryEnableCMakeCalls(&m_processScheduler);
204
break
;
205
case
IsProbingCMake:
206
// will be enabled once the CMake probing process finishes
207
break
;
208
}
209
m_workspaces.emplace_back(std::move(ws));
210
}
211
212
QQmlCodeModelManager::WorkspaceIterator
213
QQmlCodeModelManager::workspaceFromBuildFolder(
const
QString &fileName,
214
const
QStringList &buildFolders)
215
{
216
m_buildInformation.loadSettingsFrom(buildFolders);
217
const
ModuleSetting setting = m_buildInformation.settingFor(fileName);
218
QByteArray url = QUrl::fromLocalFile(setting.sourceFolder).toEncoded();
219
if
(
auto
it = findWorkspace(url); it != m_workspaces.end())
220
return
it;
221
appendWorkspace(url, ManagedByServer);
222
return
--m_workspaces.end();
223
}
224
225
void
QQmlCodeModelManager::disableCMakeCalls()
226
{
227
m_cmakeStatus = DoesNotHaveCMake;
228
for
(
const
auto
&ws : m_workspaces)
229
ws.codeModel->disableCMakeCalls();
230
}
231
232
void
QQmlCodeModelManager::cancelBackgroundBuild(
const
QByteArray &uri)
233
{
234
m_processScheduler.cancel(uri);
235
}
236
237
OpenDocumentSnapshot QQmlCodeModelManager::snapshotByUrl(
const
QByteArray &url)
238
{
239
return
findCodeModelForFile(url)->snapshotByUrl(url);
240
}
241
242
void
QQmlCodeModelManager::removeDirectory(
const
QByteArray &url)
243
{
244
findCodeModelForFile(url)->removeDirectory(url);
245
}
246
247
void
QQmlCodeModelManager::newOpenFile(
const
QByteArray &url,
int
version,
const
QString &docText)
248
{
249
const
auto
ws = findWorkspaceForFile(url);
250
m_file2CodeModel[url] = ws->url;
251
ws->codeModel->newOpenFile(url, version, docText);
252
}
253
254
OpenDocument QQmlCodeModelManager::openDocumentByUrl(
const
QByteArray &url)
255
{
256
return
findCodeModelForFile(url)->openDocumentByUrl(url);
257
}
258
259
RegisteredSemanticTokens &QQmlCodeModelManager::registeredTokens(
const
QByteArray &url)
260
{
261
return
findCodeModelForFile(url)->registeredTokens();
262
}
263
264
void
QQmlCodeModelManager::closeOpenFile(
const
QByteArray &url)
265
{
266
m_file2CodeModel.erase(url);
267
const
auto
it = findWorkspaceForFile(url);
268
it->codeModel->closeOpenFile(url);
269
270
// don't close the default workspace
271
if
(it->url.isEmpty())
272
return
;
273
274
// close empty WS when managed by server or when client marked ws as toBeClosed.
275
if
((it->managedByClient && it->toBeClosed) || !it->managedByClient) {
276
if
(it->codeModel->isEmpty())
277
m_workspaces.erase(it);
278
}
279
}
280
281
QList<QByteArray> QQmlCodeModelManager::rootUrls()
const
282
{
283
QList<QByteArray> result;
284
result.reserve(m_workspaces.size());
285
for
(
const
QQmlWorkspace &ws : m_workspaces) {
286
result << ws.url;
287
}
288
return
result;
289
}
290
291
void
QQmlCodeModelManager::addRootUrls(
const
QList<QByteArray> &urls)
292
{
293
for
(
const
QByteArray &url : urls) {
294
if
(
const
auto
it = findWorkspace(url); it != m_workspaces.end()) {
295
it->toBeClosed =
false
;
296
continue
;
297
}
298
299
appendWorkspace(url, ManagedByClient);
300
}
301
}
302
303
void
QQmlCodeModelManager::removeRootUrls(
const
QList<QByteArray> &urls)
304
{
305
for
(
const
QByteArray &url : urls) {
306
if
(
auto
it = findWorkspace(url); it != m_workspaces.end() && it->managedByClient)
307
it->toBeClosed =
true
;
308
}
309
}
310
311
QStringList QQmlCodeModelManager::importPathsForUrl(
const
QByteArray &url)
312
{
313
return
findCodeModelForFile(url)->importPathsForUrl(url);
314
}
315
316
QStringList QQmlCodeModelManager::buildPathsForFileUrl(
const
QByteArray &url)
317
{
318
return
findCodeModelForFile(url)->buildPathsForFileUrl(url);
319
}
320
321
QStringList QQmlCodeModelManager::resourceFilesForFileUrl(
const
QByteArray &url)
322
{
323
if
(
const
QStringList result =
324
m_buildInformation.resourceFilesFor(QUrl::fromEncoded(url).toLocalFile());
325
!result.isEmpty()) {
326
return
result;
327
}
328
329
// fallback, for standalone qmlls on projects targeting qt < 6.11
330
return
findCodeModelForFile(url)->resourceFiles();
331
}
332
333
QByteArray QQmlCodeModelManager::shortestRootUrlForFile(
const
QByteArray &fileUrl)
const
334
{
335
// fallback value for candidate is the empty url of the default workspace
336
QByteArray candidate;
337
338
// ignore the default workspace which is at the front of m_workspaces
339
Q_ASSERT(m_workspaces.size() > 0);
340
Q_ASSERT(m_workspaces.front().url.isEmpty());
341
auto
it = std::find_if(
342
++m_workspaces.cbegin(), m_workspaces.cend(),
343
[&fileUrl](
const
QQmlWorkspace &ws) {
return
fileUrl.startsWith(ws.url); });
344
345
if
(it != m_workspaces.cend())
346
candidate = it->url;
347
348
for
(; it != m_workspaces.cend(); ++it) {
349
if
(it->url.length() < candidate.length() && fileUrl.startsWith(it->url))
350
candidate = it->url;
351
}
352
return
candidate;
353
}
354
355
void
QQmlCodeModelManager::setDocumentationRootPath(
const
QString &path)
356
{
357
// Note: this function can only be called after the fallback workspace was created but before
358
// all other potential workspaces are created, for example when setting the import paths set via
359
// commandline option or environment variable.
360
Q_ASSERT(m_workspaces.size() == 1);
361
for
(
const
auto
&ws : m_workspaces)
362
ws.codeModel->setDocumentationRootPath(path);
363
}
364
365
void
QQmlCodeModelManager::setVerbose(
bool
verbose)
366
{
367
m_verbose = verbose;
368
for
(
const
auto
&ws : m_workspaces)
369
ws.codeModel->setVerbose(verbose);
370
}
371
372
void
QQmlCodeModelManager::setCMakeJobs(
int
jobs)
373
{
374
for
(
const
auto
&ws : m_workspaces)
375
ws.codeModel->setCMakeJobs(jobs);
376
}
377
378
void
QQmlCodeModelManager::setBuildPathsForRootUrl(
const
QByteArray &url,
const
QStringList &paths)
379
{
380
m_buildInformation.loadSettingsFrom(paths, ForceUpdate);
381
382
// build paths passed by -b have an empty url and apply to all workspaces
383
if
(url.isEmpty()) {
384
setBuildPathsOn(&*fallbackWorkspace(), paths, DontAppendPathsFromFallback);
385
// make non-fallback workspaces get the fallback build path
386
for
(
auto
it = beginNonFallbackWorkspace(), end = endNonFallbackWorkspace(); it != end;
387
++it) {
388
setBuildPathsOn(&*it, {}, AppendPathsFromFallback);
389
}
390
emit configurationChanged();
391
return
;
392
}
393
394
auto
ws = findWorkspaceForFile(url);
395
setBuildPathsOn(&*ws, paths, AppendPathsFromFallback);
396
emit configurationChanged();
397
}
398
399
void
QQmlCodeModelManager::addOpenToUpdate(
const
QByteArray &url)
400
{
401
findCodeModelForFile(url)->addOpenToUpdate(url, NormalUpdate);
402
}
403
404
void
QQmlCodeModelManager::setImportPaths(
const
QStringList &paths)
405
{
406
// Note: this function can only be called after the fallback workspace was created but before
407
// all other potential workspaces are created, for example when setting the import paths set via
408
// commandline option or environment variable.
409
Q_ASSERT(m_workspaces.size() == 1);
410
for
(
const
auto
&ws : m_workspaces)
411
ws.codeModel->setImportPaths(paths);
412
}
413
414
HelpManager *QQmlCodeModelManager::helpManagerForUrl(
const
QByteArray &url)
415
{
416
return
findCodeModelForFile(url)->helpManager();
417
}
418
419
}
// namespace QmlLsp
420
421
QT_END_NAMESPACE
QmlLSPluginInterface_iid
#define QmlLSPluginInterface_iid
Definition
qqmllsplugin_p.h:38
qtdeclarative
src
qmlls
qqmlcodemodelmanager.cpp
Generated on
for Qt by
1.18.0