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
qqmlcodemodel.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:trusted-sources
4
9
10#include <QtCore/qfileinfo.h>
11#include <QtCore/qdir.h>
12#include <QtCore/qthreadpool.h>
13#include <QtCore/qlibraryinfo.h>
14#include <QtCore/qprocess.h>
15#include <QtCore/qdiriterator.h>
16
17#if QT_CONFIG(settings)
18# include <QtCore/qsettings.h>
19#endif
20
21#include <QtQmlDom/private/qqmldomtop_p.h>
22#include <QtQmlCompiler/private/qqmljsutils_p.h>
23
24#include <memory>
25#include <algorithm>
26
28
29namespace QmlLsp {
30
31Q_STATIC_LOGGING_CATEGORY(codeModelLog, "qt.languageserver.codemodel")
32
33using namespace QQmlJS::Dom;
34using namespace Qt::StringLiterals;
35
36/*!
37\internal
38\class QQmlCodeModel
39
40The code model offers a view of the current state of the current files, and traks open files.
41All methods are threadsafe, and generally return immutable or threadsafe objects that can be
42worked on from any thread (unless otherwise noted).
43The idea is the let all other operations be as lock free as possible, concentrating all tricky
44synchronization here.
45
46\section2 Global views
47\list
48\li currentEnv() offers a view that contains the latest version of all the loaded files
49\li validEnv() is just like current env but stores only the valid (meaning correctly parsed,
50 not necessarily without errors) version of a file, it is normally a better choice to load the
51 dependencies/symbol information from
52\endlist
53
54\section2 OpenFiles
55\list
56\li snapshotByUrl() returns an OpenDocumentSnapshot of an open document. From it you can get the
57 document, its latest valid version, scope, all connected to a specific version of the document
58 and immutable. The signal updatedSnapshot() is called every time a snapshot changes (also for
59 every partial change: document change, validDocument change, scope change).
60\li openDocumentByUrl() is a lower level and more intrusive access to OpenDocument objects. These
61 contains the current snapshot, and shared pointer to a Utils::TextDocument. This is *always* the
62 current version of the document, and has line by line support.
63 Working on it is more delicate and intrusive, because you have to explicitly acquire its mutex()
64 before *any* read or write/modification to it.
65 It has a version nuber which is supposed to always change and increase.
66 It is mainly used for highlighting/indenting, and is immediately updated when the user edits a
67 document. Its use should be avoided if possible, preferring the snapshots.
68\endlist
69
70\section2 Parallelism/Theading
71Most operations are not parallel and usually take place in the main thread (but are still thread
72safe).
73There is one task that is executed in parallel: OpenDocumentUpdate.
74OpenDocumentUpdate keeps the snapshots of the open documents up to date.
75
76There is always a tension between being responsive, using all threads available, and avoid to hog
77too many resources. One can choose different parallelization strategies, we went with a flexiable
78approach.
79We have (private) functions that execute part of the work: openUpdateSome(). These
80do all locking needed, get some work, do it without locks, and at the end update the state of the
81code model. If there is more work, then they return true. Thus while (xxxSome()); works until there
82is no work left.
83
84The internal addOpenToUpdate() add more work to do.
85
86openNeedUpdate() checks if there is work to do, and if yes ensure that a
87worker thread (or more) that work on it exist.
88*/
89
103
104/*!
105\internal
106Enable and initialize the functionality that uses CMake, if CMake exists. Runs the build once.
107*/
109{
110 Q_ASSERT(scheduler);
111 if (cmakeStatus() == DoesNotHaveCMake)
112 return;
113
114 if (m_settings) {
115 const QString cmakeCalls = u"no-cmake-calls"_s;
116 m_settings->search(url2Path(m_rootUrl), { QString(), verbose() });
117 if (m_settings->isSet(cmakeCalls) && m_settings->value(cmakeCalls).toBool()) {
119 return;
120 }
121 }
122
123 QObject::connect(scheduler, &QProcessScheduler::done, this,
124 &QQmlCodeModel::onCMakeProcessFinished);
125 QObject::connect(&m_cppFileWatcher, &QFileSystemWatcher::fileChanged, scheduler,
126 [this, scheduler] { callCMakeBuild(scheduler); });
127 setCMakeStatus(HasCMake);
128 callCMakeBuild(scheduler);
129}
130
131/*!
132\internal
133Disable the functionality that uses CMake, and remove the already watched paths if there are some.
134*/
136{
137 QMutexLocker guard(&m_mutex);
138 m_cmakeStatus = DoesNotHaveCMake;
139 if (const QStringList toRemove = m_cppFileWatcher.files(); !toRemove.isEmpty())
140 m_cppFileWatcher.removePaths(toRemove);
141 QObject::disconnect(&m_cppFileWatcher, &QFileSystemWatcher::fileChanged, nullptr, nullptr);
142}
143
145{
146 QObject::disconnect(&m_cppFileWatcher, &QFileSystemWatcher::fileChanged, nullptr, nullptr);
147 while (true) {
148 bool shouldWait;
149 {
150 QMutexLocker l(&m_mutex);
151 m_openDocumentsToUpdate.clear();
152 shouldWait = m_nUpdateInProgress != 0;
153 }
154 if (!shouldWait)
155 break;
156 QThread::yieldCurrentThread();
157 }
158}
159
161{
162 return openDocumentByUrl(url).snapshot;
163}
164
165void QQmlCodeModel::removeDirectory(const QByteArray &url)
166{
167 const QString path = url2Path(url);
168 if (auto validEnvPtr = m_validEnv.ownerAs<DomEnvironment>())
169 validEnvPtr->removePath(path);
170 if (auto currentEnvPtr = m_currentEnv.ownerAs<DomEnvironment>())
171 currentEnvPtr->removePath(path);
172}
173
174QString QQmlCodeModel::url2Path(const QByteArray &url, UrlLookup options)
175{
176 QString res;
177 {
178 QMutexLocker l(&m_mutex);
179 res = m_url2path.value(url);
180 }
181 if (!res.isEmpty() && options == UrlLookup::Caching)
182 return res;
183 QUrl qurl(QString::fromUtf8(url));
184 QFileInfo f(qurl.toLocalFile());
185 QString cPath = f.canonicalFilePath();
186 if (cPath.isEmpty())
187 cPath = f.filePath();
188 {
189 QMutexLocker l(&m_mutex);
190 if (!res.isEmpty() && res != cPath)
191 m_path2url.remove(res);
192 m_url2path.insert(url, cPath);
193 m_path2url.insert(cPath, url);
194 }
195 return cPath;
196}
197
198void QQmlCodeModel::newOpenFile(const QByteArray &url, int version, const QString &docText)
199{
200 {
201 QMutexLocker l(&m_mutex);
202 auto &openDoc = m_openDocuments[url];
203 if (!openDoc.textDocument)
204 openDoc.textDocument = std::make_shared<Utils::TextDocument>();
205 QMutexLocker l2(openDoc.textDocument->mutex());
206 openDoc.textDocument->setVersion(version);
207 openDoc.textDocument->setPlainText(docText);
208 }
209 addOpenToUpdate(url, NormalUpdate);
210 openNeedUpdate();
211}
212
214{
215 QMutexLocker l(&m_mutex);
216 return m_openDocuments.value(url);
217}
218
220{
221 QMutexLocker l(&m_mutex);
222 return m_openDocuments.isEmpty();
223}
224
226{
227 QMutexLocker l(&m_mutex);
228 return m_tokens;
229}
230
232{
233 QMutexLocker l(&m_mutex);
234 return m_tokens;
235}
236
237void QQmlCodeModel::openNeedUpdate()
238{
239 qCDebug(codeModelLog) << "openNeedUpdate";
240 const int maxThreads = 1;
241 {
242 QMutexLocker l(&m_mutex);
243 if (m_openDocumentsToUpdate.isEmpty() || m_nUpdateInProgress >= maxThreads)
244 return;
245 if (++m_nUpdateInProgress == 1)
246 openUpdateStart();
247 }
248 QThreadPool::globalInstance()->start([this]() {
249 while (openUpdateSome()) { }
250 });
251}
252
253bool QQmlCodeModel::openUpdateSome()
254{
255 qCDebug(codeModelLog) << "openUpdateSome start";
256 QByteArray toUpdate;
257 UpdatePolicy policy;
258 {
259 QMutexLocker l(&m_mutex);
260 if (m_openDocumentsToUpdate.isEmpty()) {
261 if (--m_nUpdateInProgress == 0)
262 openUpdateEnd();
263 return false;
264 }
265 const auto it = m_openDocumentsToUpdate.begin();
266 toUpdate = it.key();
267 policy = it.value();
268 m_openDocumentsToUpdate.erase(it);
269 }
270 bool hasMore = false;
271 {
272 auto guard = qScopeGuard([this, &hasMore]() {
273 QMutexLocker l(&m_mutex);
274 if (m_openDocumentsToUpdate.isEmpty()) {
275 if (--m_nUpdateInProgress == 0)
276 openUpdateEnd();
277 hasMore = false;
278 } else {
279 hasMore = true;
280 }
281 });
282 openUpdate(toUpdate, policy);
283 }
284 return hasMore;
285}
286
287void QQmlCodeModel::openUpdateStart()
288{
289 qCDebug(codeModelLog) << "openUpdateStart";
290}
291
292void QQmlCodeModel::openUpdateEnd()
293{
294 qCDebug(codeModelLog) << "openUpdateEnd";
295}
296
298{
299 QStringList result;
300
301 std::vector<QByteArray> urls = { m_rootUrl };
302 {
303 QMutexLocker guard(&m_mutex);
304 for (auto it = m_openDocuments.begin(), end = m_openDocuments.end(); it != end; ++it)
305 urls.push_back(it.key());
306 }
307
308 for (const auto &url : urls)
309 result.append(buildPathsForFileUrl(url));
310
311 // remove duplicates
312 std::sort(result.begin(), result.end());
313 result.erase(std::unique(result.begin(), result.end()), result.end());
314 return result;
315}
316
317void QQmlCodeModel::callCMakeBuild(QProcessScheduler *scheduler)
318{
319 const QStringList buildPaths = buildPathsForOpenedFiles();
320
321 QList<QProcessScheduler::Command> commands;
322 for (const auto &path : buildPaths) {
323 if (!QFileInfo::exists(path + u"/.cmake"_s))
324 continue;
325
326 const auto [program, arguments] = QQmlLSUtils::cmakeBuildCommand(path);
327 commands.append({ std::move(program), std::move(arguments) });
328 }
329 if (commands.isEmpty())
330 return;
331 scheduler->schedule(commands, m_rootUrl);
332}
333
334void QQmlCodeModel::onCMakeProcessFinished(const QByteArray &id)
335{
336 if (id != m_rootUrl)
337 return;
338
339 QMutexLocker guard(&m_mutex);
340 for (auto it = m_openDocuments.begin(), end = m_openDocuments.end(); it != end; ++it)
341 m_openDocumentsToUpdate[it.key()] = ForceUpdate;
342 guard.unlock();
343 openNeedUpdate();
344}
345
346/*!
347\internal
348Iterate the entire source directory to find all C++ files that have their names in fileNames, and
349return all the found file paths.
350
351This is an overapproximation and might find unrelated files with the same name.
352*/
353QStringList QQmlCodeModel::findFilePathsFromFileNames(const QStringList &_fileNamesToSearch,
354 const QSet<QString> &ignoredFilePaths)
355{
356 Q_ASSERT(!m_rootUrl.isEmpty());
357 QStringList fileNamesToSearch{ _fileNamesToSearch };
358
359 {
360 QMutexLocker guard(&m_mutex);
361 // ignore files that were not found last time
362 fileNamesToSearch.erase(std::remove_if(fileNamesToSearch.begin(), fileNamesToSearch.end(),
363 [this](const QString &fileName) {
364 return m_ignoreForWatching.contains(fileName);
365 }),
366 fileNamesToSearch.end());
367 }
368
369 const QString rootDir = QUrl(QString::fromUtf8(m_rootUrl)).toLocalFile();
370 qCDebug(codeModelLog) << "Searching for files to watch in workspace folder" << rootDir;
371
372 const QStringList result =
373 QQmlLSUtils::findFilePathsFromFileNames(rootDir, fileNamesToSearch, ignoredFilePaths);
374
375 QMutexLocker guard(&m_mutex);
376 for (const auto &fileName : fileNamesToSearch) {
377 if (std::none_of(result.begin(), result.end(),
378 [&fileName](const QString &path) { return path.endsWith(fileName); })) {
379 m_ignoreForWatching.insert(fileName);
380 }
381 }
382 return result;
383}
384
385/*!
386\internal
387Find all C++ file names (not path, for file paths call \l findFilePathsFromFileNames on the result
388of this method) that this qmlFile relies on.
389*/
390QStringList QQmlCodeModel::fileNamesToWatch(const DomItem &qmlFile)
391{
392 const QmlFile *file = qmlFile.as<QmlFile>();
393 if (!file)
394 return {};
395
396 auto resolver = file->typeResolver();
397 if (!resolver)
398 return {};
399
400 auto types = resolver->importedTypes();
401
402 QStringList result;
403 for (const auto &type : types) {
404 if (!type.scope)
405 continue;
406 // note: the factory only loads composite types
407 const bool isComposite = type.scope.factory() || type.scope->isComposite();
408 if (isComposite)
409 continue;
410
411 const QString filePath = QFileInfo(type.scope->filePath()).fileName();
412 if (!filePath.isEmpty())
413 result << filePath;
414 }
415
416 std::sort(result.begin(), result.end());
417 result.erase(std::unique(result.begin(), result.end()), result.end());
418
419 return result;
420}
421
422/*!
423\internal
424Add watches for all C++ files that this qmlFile relies on, so a rebuild can be triggered when they
425are modified. Is a no op if this is the fallback codemodel with empty root url.
426*/
427void QQmlCodeModel::addFileWatches(const DomItem &qmlFile)
428{
429 if (m_rootUrl.isEmpty())
430 return;
431 const auto filesToWatch = fileNamesToWatch(qmlFile);
432
433 // remove already watched files to avoid a warning later on
434 const QStringList alreadyWatchedFiles = m_cppFileWatcher.files();
435 const QSet<QString> alreadyWatchedFilesSet(alreadyWatchedFiles.begin(),
436 alreadyWatchedFiles.end());
437 QStringList filepathsToWatch = findFilePathsFromFileNames(filesToWatch, alreadyWatchedFilesSet);
438
439 if (filepathsToWatch.isEmpty())
440 return;
441
442 QMutexLocker guard(&m_mutex);
443 const auto unwatchedPaths = m_cppFileWatcher.addPaths(filepathsToWatch);
444 if (!unwatchedPaths.isEmpty()) {
445 qCDebug(codeModelLog) << "Cannot watch paths" << unwatchedPaths << "from requested"
446 << filepathsToWatch;
447 }
448}
449
456
461
463{
464 if (!doc.textDocument)
465 return ClosedDocument;
466
467 {
468 QMutexLocker guard2(doc.textDocument->mutex());
469 if (doc.textDocument->version() && *doc.textDocument->version() > version)
471 }
472
473 if (doc.snapshot.docVersion && *doc.snapshot.docVersion >= version)
475
476 return VersionOk;
477}
478
480 int version)
481{
482 if (doc.snapshot.validDocVersion && *doc.snapshot.validDocVersion >= version)
484
486}
487
488static void updateItemInSnapshot(const DomItem &item, const DomItem &validItem,
489 const QByteArray &url, OpenDocument *doc, int version,
490 UpdatePolicy policy)
491{
492 switch (policy == ForceUpdate ? VersionOk : checkVersion(*doc, version)) {
493 case ClosedDocument:
494 qCWarning(lspServerLog) << "Ignoring update to closed document" << QString::fromUtf8(url);
495 return;
496 case VersionLowerThanDocument:
497 qCWarning(lspServerLog) << "Version" << version << "of document" << QString::fromUtf8(url)
498 << "is not the latest anymore";
499 return;
500 case VersionLowerThanSnapshot:
501 qCWarning(lspServerLog) << "Skipping update of current doc to obsolete version" << version
502 << "of document" << QString::fromUtf8(url);
503 return;
504 case VersionOk:
505 doc->snapshot.docVersion = version;
506 doc->snapshot.doc = item;
507 break;
508 }
509
510 if (!item.field(Fields::isValid).value().toBool(false)) {
511 qCWarning(lspServerLog) << "avoid update of validDoc to " << version << "of document"
512 << QString::fromUtf8(url) << "as it is invalid";
513 return;
514 }
515
516 switch (policy == ForceUpdate ? VersionOkForValidDocument
517 : checkVersionForValidDocument(*doc, version)) {
518 case VersionLowerThanValidSnapshot:
519 qCWarning(lspServerLog) << "Skipping update of valid doc to obsolete version" << version
520 << "of document" << QString::fromUtf8(url);
521 return;
523 doc->snapshot.validDocVersion = version;
524 doc->snapshot.validDoc = validItem;
525 break;
526 }
527}
528
529void QQmlCodeModel::newDocForOpenFile(const QByteArray &url, int version, const QString &docText,
530 QmlLsp::UpdatePolicy policy)
531{
532 qCDebug(codeModelLog) << "updating doc" << url << "to version" << version << "("
533 << docText.size() << "chars)";
534
535 const QString fPath = url2Path(url, UrlLookup::ForceLookup);
536 DomItem newCurrent = m_currentEnv.makeCopy(DomItem::CopyOption::EnvConnected).item();
537
538 // if the documentation root path is not set through the commandline,
539 // try to set it from the settings file (.qmlls.ini file)
540 if (documentationRootPath().isEmpty() && m_settings) {
541 // note: settings already searched current file in importPathsForFile() call above
542 const QString docDir = QStringLiteral(u"docDir");
543 if (m_settings->isSet(docDir))
544 setDocumentationRootPath(m_settings->value(docDir).toString());
545 }
546
547 Path p;
548 auto newCurrentPtr = newCurrent.ownerAs<DomEnvironment>();
549 const QStringList loadPaths = buildPathsForFileUrl(url) + importPathsForUrl(url);
550 newCurrentPtr->setLoadPaths(loadPaths);
551 newCurrentPtr->loadFile(FileToLoad::fromMemory(newCurrentPtr, fPath, docText),
552 [&p, this](Path, const DomItem &, const DomItem &newValue) {
553 const DomItem file = newValue.fileObject();
554 p = file.canonicalPath();
555 // Force population of the file by accessing isValid field. We
556 // don't want to populate the file after adding the file to the
557 // snapshot in updateItemInSnapshot.
558 file.field(Fields::isValid);
559 if (cmakeStatus() == HasCMake)
560 addFileWatches(file);
561 });
562 newCurrentPtr->loadPendingDependencies();
563 if (p) {
564 newCurrent.commitToBase(m_validEnv.ownerAs<DomEnvironment>());
565 QMutexLocker l(&m_mutex);
566 updateItemInSnapshot(m_currentEnv.path(p), m_validEnv.path(p), url, &m_openDocuments[url],
567 version, policy);
568 }
569 if (codeModelLog().isDebugEnabled()) {
570 qCDebug(codeModelLog) << "Finished update doc of " << url << "to version" << version;
571 snapshotByUrl(url).dump(qDebug() << "postSnapshot",
572 OpenDocumentSnapshot::DumpOption::AllCode);
573 }
574 // we should update the scope in the future thus call addOpen(url)
575 emit updatedSnapshot(url, policy);
576}
577
578void QQmlCodeModel::closeOpenFile(const QByteArray &url)
579{
580 QMutexLocker l(&m_mutex);
581 m_openDocuments.remove(url);
582}
583
585{
586 QMutexLocker l(&m_mutex);
587 return m_rootUrl;
588}
589
591{
592 QMutexLocker l(&m_mutex);
593 return m_buildPaths;
594}
595
597{
598 QStringList result = importPaths();
599
600 const QString importPathsKey = u"importPaths"_s;
601 const QString fileName = url2Path(url);
602 if (m_settings && m_settings->search(fileName, { QString(), verbose() }).isValid()
603 && m_settings->isSet(importPathsKey)) {
604 result.append(m_settings->valueAsAbsolutePathList(importPathsKey, fileName));
605 }
606
607 const QStringList buildPath = buildPathsForFileUrl(url);
608 QMutexLocker l(&m_mutex);
609 m_buildInformation.loadSettingsFrom(buildPath);
610 result.append(m_buildInformation.importPathsFor(fileName));
611
612 return result;
613}
614
615void QQmlCodeModel::onBuildOrImportPathChanged()
616{
617 const QStringList loadPaths = m_importPaths + m_buildPaths;
618 if (const auto &env = m_currentEnv.ownerAs<DomEnvironment>())
619 env->setLoadPaths(loadPaths);
620 if (const auto &env = m_validEnv.ownerAs<DomEnvironment>())
621 env->setLoadPaths(loadPaths);
622}
623
624void QQmlCodeModel::setImportPaths(const QStringList &importPaths)
625{
626 QMutexLocker guard(&m_mutex);
627 m_importPaths = importPaths;
628 onBuildOrImportPathChanged();
629}
630
632{
633 QMutexLocker guard(&m_mutex);
634 return m_importPaths;
635}
636
637static QStringList withDependentBuildDirectories(QStringList &&buildPaths)
638{
639 // add dependent build directories
640 QStringList res;
641 std::reverse(buildPaths.begin(), buildPaths.end());
642 const int maxDeps = 4;
643 while (!buildPaths.isEmpty()) {
644 res += buildPaths.takeLast();
645 const QString &bPath = res.constLast();
646 const QString bPathExtended = bPath + u"/_deps";
647 if (QFile::exists(bPathExtended) && bPath.count(u"/_deps/"_s) < maxDeps) {
648 for (const auto &fileInfo :
649 QDirListing{ bPathExtended, QDirListing::IteratorFlag::DirsOnly }) {
650 buildPaths.append(fileInfo.absoluteFilePath());
651 }
652 }
653 }
654 return res;
655}
656
658{
659 if (QStringList result = buildPaths(); !result.isEmpty())
660 return withDependentBuildDirectories(std::move(result));
661
662 // fallback: look in the user settings (.qmlls.ini files in the source directory)
663 if (!m_settings || !m_settings->search(url2Path(url), { QString(), verbose() }).isValid())
664 return {};
665
666 constexpr QLatin1String buildDir = "buildDir"_L1;
667 if (!m_settings->isSet(buildDir))
668 return {};
669
670 return withDependentBuildDirectories(m_settings->value(buildDir).toString().split(
671 QDir::listSeparator(), Qt::SkipEmptyParts));
672}
673
674void QQmlCodeModel::setDocumentationRootPath(const QString &path)
675{
676 {
677 QMutexLocker l(&m_mutex);
678 if (m_documentationRootPath == path)
679 return;
680 m_documentationRootPath = path;
681 }
682 m_helpManager.setDocumentationRootPath(path);
683}
684
685void QQmlCodeModel::setBuildPaths(const QStringList &paths)
686{
687 QMutexLocker l(&m_mutex);
688 m_buildPaths = paths;
689 onBuildOrImportPathChanged();
690}
691
692void QQmlCodeModel::openUpdate(const QByteArray &url, UpdatePolicy policy)
693{
694 std::optional<int> rNow = 0;
695 QString docText;
696
697 {
698 QMutexLocker l(&m_mutex);
699 OpenDocument &doc = m_openDocuments[url];
700 std::shared_ptr<Utils::TextDocument> document = doc.textDocument;
701 if (!document)
702 return;
703 {
704 QMutexLocker l2(document->mutex());
705 rNow = document->version();
706 }
707 if (!rNow
708 || (policy != ForceUpdate && doc.snapshot.docVersion
709 && *doc.snapshot.docVersion == *rNow)) {
710 return;
711 }
712
713 {
714 QMutexLocker l2(doc.textDocument->mutex());
715 rNow = doc.textDocument->version();
716 docText = doc.textDocument->toPlainText();
717 }
718 }
719 newDocForOpenFile(url, *rNow, docText, policy);
720}
721
722void QQmlCodeModel::addOpenToUpdate(const QByteArray &url, UpdatePolicy policy)
723{
724 {
725 QMutexLocker l(&m_mutex);
726 m_openDocumentsToUpdate[url] = policy;
727 }
728 openNeedUpdate();
729}
730
731QDebug OpenDocumentSnapshot::dump(QDebug dbg, DumpOptions options)
732{
733 dbg.noquote().nospace() << "{";
734 dbg << " url:" << QString::fromUtf8(url) << "\n";
735 dbg << " docVersion:" << (docVersion ? QString::number(*docVersion) : u"*none*"_s) << "\n";
736 if (options & DumpOption::LatestCode) {
737 dbg << " doc: ------------\n"
738 << doc.field(Fields::code).value().toString() << "\n==========\n";
739 } else {
740 dbg << u" doc:"
741 << (doc ? u"%1chars"_s.arg(doc.field(Fields::code).value().toString().size())
742 : u"*none*"_s)
743 << "\n";
744 }
745 dbg << " validDocVersion:"
746 << (validDocVersion ? QString::number(*validDocVersion) : u"*none*"_s) << "\n";
747 if (options & DumpOption::ValidCode) {
748 dbg << " validDoc: ------------\n"
749 << validDoc.field(Fields::code).value().toString() << "\n==========\n";
750 } else {
751 dbg << u" validDoc:"
752 << (validDoc ? u"%1chars"_s.arg(validDoc.field(Fields::code).value().toString().size())
753 : u"*none*"_s)
754 << "\n";
755 }
756 dbg << " scopeDependenciesLoadTime:" << scopeDependenciesLoadTime << "\n";
757 dbg << " scopeDependenciesChanged" << scopeDependenciesChanged << "\n";
758 dbg << "}";
759 return dbg;
760}
761
762void QQmllsBuildInformation::loadSettingsFrom(const QStringList &buildPaths)
763{
764#if QT_CONFIG(settings)
765 for (const QString &path : buildPaths) {
766 if (m_seenSettings.contains(path))
767 continue;
768 m_seenSettings.insert(path);
769
770 const QString iniPath = QString(path).append("/.qt/.qmlls.build.ini"_L1);
771 if (!QFile::exists(iniPath))
772 continue;
773
774 QSettings settings(iniPath, QSettings::IniFormat);
775 m_docDir = settings.value("docDir"_L1).toString();
776 for (const QString &group : settings.childGroups()) {
777 settings.beginGroup(group);
778
779 ModuleSetting moduleSetting;
780 moduleSetting.sourceFolder = group;
781 moduleSetting.sourceFolder.replace("<SLASH>"_L1, "/"_L1);
782 moduleSetting.importPaths = settings.value("importPaths"_L1)
783 .toString()
784 .split(QDir::listSeparator(), Qt::SkipEmptyParts);
785 m_moduleSettings.append(moduleSetting);
786 settings.endGroup();
787 }
788 }
789#else
790 Q_UNUSED(buildPaths);
791#endif
792}
793
795{
796 return settingFor(filePath).importPaths;
797}
798
800{
801 ModuleSetting result;
802 qsizetype longestMatch = 0;
803 for (const ModuleSetting &setting : m_moduleSettings) {
804 const qsizetype matchLength = setting.sourceFolder.size();
805 if (filePath.startsWith(setting.sourceFolder) && matchLength > longestMatch) {
806 result = setting;
807 longestMatch = matchLength;
808 }
809 }
810 QQmlToolingSettings::resolveRelativeImportPaths(filePath, &result.importPaths);
811 return result;
812}
813
815
816} // namespace QmlLsp
817
818QT_END_NAMESPACE
QDebug dump(QDebug dbg, DumpOptions dump=DumpOption::NoCode)
Runs multiple processes sequentially via a QProcess, and signals once they are done.
void newOpenFile(const QByteArray &url, int version, const QString &docText)
void newDocForOpenFile(const QByteArray &url, int version, const QString &docText, UpdatePolicy policy)
void addOpenToUpdate(const QByteArray &, UpdatePolicy policy)
OpenDocumentSnapshot snapshotByUrl(const QByteArray &url)
void tryEnableCMakeCalls(QProcessScheduler *scheduler)
void setDocumentationRootPath(const QString &path)
QByteArray rootUrl() const
QStringList importPaths() const
OpenDocument openDocumentByUrl(const QByteArray &url)
QStringList findFilePathsFromFileNames(const QStringList &fileNames, const QSet< QString > &alreadyWatchedFiles)
QQmlCodeModel(const QByteArray &rootUrl={}, QObject *parent=nullptr, QQmlToolingSharedSettings *settings=nullptr)
const RegisteredSemanticTokens & registeredTokens() const
QStringList buildPathsForFileUrl(const QByteArray &url)
QStringList importPathsForUrl(const QByteArray &)
QStringList buildPathsForOpenedFiles()
void setBuildPaths(const QStringList &paths)
void closeOpenFile(const QByteArray &url)
RegisteredSemanticTokens & registeredTokens()
void removeDirectory(const QByteArray &)
void setImportPaths(const QStringList &paths)
QStringList importPathsFor(const QString &filePath)
void loadSettingsFrom(const QStringList &buildPaths)
ModuleSetting settingFor(const QString &filePath)
Combined button and popup list for selecting options.
VersionCheckResult checkVersion(const OpenDocument &doc, int version)
static QStringList withDependentBuildDirectories(QStringList &&buildPaths)
VersionCheckResultForValidDocument
@ VersionLowerThanValidSnapshot
@ VersionOkForValidDocument
static VersionCheckResultForValidDocument checkVersionForValidDocument(const OpenDocument &doc, int version)
@ VersionLowerThanSnapshot
@ VersionLowerThanDocument
static void updateItemInSnapshot(const DomItem &item, const DomItem &validItem, const QByteArray &url, OpenDocument *doc, int version, UpdatePolicy policy)