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.
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
128 setCMakeStatus(HasCMake);
129
130 // TODO: call CMake build here, to automatically build on the initial workspace opening
131}
132
133/*!
134\internal
135Disable the functionality that uses CMake, and remove the already watched paths if there are some.
136*/
138{
139 QMutexLocker guard(&m_mutex);
140 m_cmakeStatus = DoesNotHaveCMake;
141 if (const QStringList toRemove = m_cppFileWatcher.files(); !toRemove.isEmpty())
142 m_cppFileWatcher.removePaths(toRemove);
143 QObject::disconnect(&m_cppFileWatcher, &QFileSystemWatcher::fileChanged, nullptr, nullptr);
144}
145
147{
148 QObject::disconnect(&m_cppFileWatcher, &QFileSystemWatcher::fileChanged, nullptr, nullptr);
149 while (true) {
150 bool shouldWait;
151 {
152 QMutexLocker l(&m_mutex);
153 m_openDocumentsToUpdate.clear();
154 shouldWait = m_nUpdateInProgress != 0;
155 }
156 if (!shouldWait)
157 break;
158 QThread::yieldCurrentThread();
159 }
160}
161
163{
164 return openDocumentByUrl(url).snapshot;
165}
166
167void QQmlCodeModel::removeDirectory(const QByteArray &url)
168{
169 const QString path = url2Path(url);
170 if (auto validEnvPtr = m_validEnv.ownerAs<DomEnvironment>())
171 validEnvPtr->removePath(path);
172 if (auto currentEnvPtr = m_currentEnv.ownerAs<DomEnvironment>())
173 currentEnvPtr->removePath(path);
174}
175
176QString QQmlCodeModel::url2Path(const QByteArray &url, UrlLookup options)
177{
178 QString res;
179 {
180 QMutexLocker l(&m_mutex);
181 res = m_url2path.value(url);
182 }
183 if (!res.isEmpty() && options == UrlLookup::Caching)
184 return res;
185 QUrl qurl(QString::fromUtf8(url));
186 QFileInfo f(qurl.toLocalFile());
187 QString cPath = f.canonicalFilePath();
188 if (cPath.isEmpty())
189 cPath = f.filePath();
190 {
191 QMutexLocker l(&m_mutex);
192 if (!res.isEmpty() && res != cPath)
193 m_path2url.remove(res);
194 m_url2path.insert(url, cPath);
195 m_path2url.insert(cPath, url);
196 }
197 return cPath;
198}
199
200void QQmlCodeModel::newOpenFile(const QByteArray &url, int version, const QString &docText)
201{
202 {
203 QMutexLocker l(&m_mutex);
204 auto &openDoc = m_openDocuments[url];
205 if (!openDoc.textDocument)
206 openDoc.textDocument = std::make_shared<Utils::TextDocument>();
207 QMutexLocker l2(openDoc.textDocument->mutex());
208 openDoc.textDocument->setVersion(version);
209 openDoc.textDocument->setPlainText(docText);
210 }
211 addOpenToUpdate(url, NormalUpdate);
212 openNeedUpdate();
213}
214
216{
217 QMutexLocker l(&m_mutex);
218 return m_openDocuments.value(url);
219}
220
222{
223 QMutexLocker l(&m_mutex);
224 return m_openDocuments.isEmpty();
225}
226
228{
229 QMutexLocker l(&m_mutex);
230 return m_tokens;
231}
232
234{
235 QMutexLocker l(&m_mutex);
236 return m_tokens;
237}
238
239void QQmlCodeModel::openNeedUpdate()
240{
241 qCDebug(codeModelLog) << "openNeedUpdate";
242 const int maxThreads = 1;
243 {
244 QMutexLocker l(&m_mutex);
245 if (m_openDocumentsToUpdate.isEmpty() || m_nUpdateInProgress >= maxThreads)
246 return;
247 if (++m_nUpdateInProgress == 1)
248 openUpdateStart();
249 }
250 QThreadPool::globalInstance()->start([this]() {
251 while (openUpdateSome()) { }
252 });
253}
254
255bool QQmlCodeModel::openUpdateSome()
256{
257 qCDebug(codeModelLog) << "openUpdateSome start";
258 QByteArray toUpdate;
259 UpdatePolicy policy;
260 {
261 QMutexLocker l(&m_mutex);
262 if (m_openDocumentsToUpdate.isEmpty()) {
263 if (--m_nUpdateInProgress == 0)
264 openUpdateEnd();
265 return false;
266 }
267 const auto it = m_openDocumentsToUpdate.begin();
268 toUpdate = it.key();
269 policy = it.value();
270 m_openDocumentsToUpdate.erase(it);
271 }
272 bool hasMore = false;
273 {
274 auto guard = qScopeGuard([this, &hasMore]() {
275 QMutexLocker l(&m_mutex);
276 if (m_openDocumentsToUpdate.isEmpty()) {
277 if (--m_nUpdateInProgress == 0)
278 openUpdateEnd();
279 hasMore = false;
280 } else {
281 hasMore = true;
282 }
283 });
284 openUpdate(toUpdate, policy);
285 }
286 return hasMore;
287}
288
289void QQmlCodeModel::openUpdateStart()
290{
291 qCDebug(codeModelLog) << "openUpdateStart";
292}
293
294void QQmlCodeModel::openUpdateEnd()
295{
296 qCDebug(codeModelLog) << "openUpdateEnd";
297}
298
300{
301 QStringList result;
302
303 std::vector<QByteArray> urls = { m_rootUrl };
304 {
305 QMutexLocker guard(&m_mutex);
306 for (auto it = m_openDocuments.begin(), end = m_openDocuments.end(); it != end; ++it)
307 urls.push_back(it.key());
308 }
309
310 for (const auto &url : urls)
311 result.append(buildPathsForFileUrl(url));
312
313 // remove duplicates
314 std::sort(result.begin(), result.end());
315 result.erase(std::unique(result.begin(), result.end()), result.end());
316 return result;
317}
318
319void QQmlCodeModel::callCMakeBuild(QProcessScheduler *scheduler)
320{
321 const QStringList buildPaths = buildPathsForOpenedFiles();
322
323 QList<QProcessScheduler::Command> commands;
324 for (const auto &path : buildPaths) {
325 if (!QFileInfo::exists(path + u"/.cmake"_s))
326 continue;
327
328 const auto [program, arguments] = QQmlLSUtils::cmakeBuildCommand(path);
329 commands.append({ std::move(program), std::move(arguments) });
330 }
331 if (commands.isEmpty())
332 return;
333 scheduler->schedule(commands, m_rootUrl);
334}
335
336void QQmlCodeModel::onCMakeProcessFinished(const QByteArray &id)
337{
338 if (id != m_rootUrl)
339 return;
340
341 QMutexLocker guard(&m_mutex);
342 for (auto it = m_openDocuments.begin(), end = m_openDocuments.end(); it != end; ++it)
343 m_openDocumentsToUpdate[it.key()] = ForceUpdate;
344 guard.unlock();
345 openNeedUpdate();
346}
347
348/*!
349\internal
350Iterate the entire source directory to find all C++ files that have their names in fileNames, and
351return all the found file paths.
352
353This is an overapproximation and might find unrelated files with the same name.
354*/
355QStringList QQmlCodeModel::findFilePathsFromFileNames(const QStringList &_fileNamesToSearch,
356 const QSet<QString> &ignoredFilePaths)
357{
358 Q_ASSERT(!m_rootUrl.isEmpty());
359 QStringList fileNamesToSearch{ _fileNamesToSearch };
360
361 {
362 QMutexLocker guard(&m_mutex);
363 // ignore files that were not found last time
364 fileNamesToSearch.erase(std::remove_if(fileNamesToSearch.begin(), fileNamesToSearch.end(),
365 [this](const QString &fileName) {
366 return m_ignoreForWatching.contains(fileName);
367 }),
368 fileNamesToSearch.end());
369 }
370
371 const QString rootDir = QUrl(QString::fromUtf8(m_rootUrl)).toLocalFile();
372 qCDebug(codeModelLog) << "Searching for files to watch in workspace folder" << rootDir;
373
374 const QStringList result =
375 QQmlLSUtils::findFilePathsFromFileNames(rootDir, fileNamesToSearch, ignoredFilePaths);
376
377 QMutexLocker guard(&m_mutex);
378 for (const auto &fileName : fileNamesToSearch) {
379 if (std::none_of(result.begin(), result.end(),
380 [&fileName](const QString &path) { return path.endsWith(fileName); })) {
381 m_ignoreForWatching.insert(fileName);
382 }
383 }
384 return result;
385}
386
387/*!
388\internal
389Find all C++ file names (not path, for file paths call \l findFilePathsFromFileNames on the result
390of this method) that this qmlFile relies on.
391*/
392QStringList QQmlCodeModel::fileNamesToWatch(const DomItem &qmlFile)
393{
394 const QmlFile *file = qmlFile.as<QmlFile>();
395 if (!file)
396 return {};
397
398 auto resolver = file->typeResolver();
399 if (!resolver)
400 return {};
401
402 auto types = resolver->importedTypes();
403
404 QStringList result;
405 for (const auto &type : types) {
406 if (!type.scope)
407 continue;
408 // note: the factory only loads composite types
409 const bool isComposite = type.scope.factory() || type.scope->isComposite();
410 if (isComposite)
411 continue;
412
413 const QString filePath = QFileInfo(type.scope->filePath()).fileName();
414 if (!filePath.isEmpty())
415 result << filePath;
416 }
417
418 std::sort(result.begin(), result.end());
419 result.erase(std::unique(result.begin(), result.end()), result.end());
420
421 return result;
422}
423
424/*!
425\internal
426Add watches for all C++ files that this qmlFile relies on, so a rebuild can be triggered when they
427are modified. Is a no op if this is the fallback codemodel with empty root url.
428*/
429void QQmlCodeModel::addFileWatches(const DomItem &qmlFile)
430{
431 if (m_rootUrl.isEmpty())
432 return;
433 const auto filesToWatch = fileNamesToWatch(qmlFile);
434
435 // remove already watched files to avoid a warning later on
436 const QStringList alreadyWatchedFiles = m_cppFileWatcher.files();
437 const QSet<QString> alreadyWatchedFilesSet(alreadyWatchedFiles.begin(),
438 alreadyWatchedFiles.end());
439 QStringList filepathsToWatch = findFilePathsFromFileNames(filesToWatch, alreadyWatchedFilesSet);
440
441 if (filepathsToWatch.isEmpty())
442 return;
443
444 QMutexLocker guard(&m_mutex);
445 const auto unwatchedPaths = m_cppFileWatcher.addPaths(filepathsToWatch);
446 if (!unwatchedPaths.isEmpty()) {
447 qCDebug(codeModelLog) << "Cannot watch paths" << unwatchedPaths << "from requested"
448 << filepathsToWatch;
449 }
450}
451
458
463
465{
466 if (!doc.textDocument)
467 return ClosedDocument;
468
469 {
470 QMutexLocker guard2(doc.textDocument->mutex());
471 if (doc.textDocument->version() && *doc.textDocument->version() > version)
473 }
474
475 if (doc.snapshot.docVersion && *doc.snapshot.docVersion >= version)
477
478 return VersionOk;
479}
480
482 int version)
483{
484 if (doc.snapshot.validDocVersion && *doc.snapshot.validDocVersion >= version)
486
488}
489
490static void updateItemInSnapshot(const DomItem &item, const DomItem &validItem,
491 const QByteArray &url, OpenDocument *doc, int version,
492 UpdatePolicy policy)
493{
494 switch (policy == ForceUpdate ? VersionOk : checkVersion(*doc, version)) {
495 case ClosedDocument:
496 qCWarning(lspServerLog) << "Ignoring update to closed document" << QString::fromUtf8(url);
497 return;
498 case VersionLowerThanDocument:
499 qCWarning(lspServerLog) << "Version" << version << "of document" << QString::fromUtf8(url)
500 << "is not the latest anymore";
501 return;
502 case VersionLowerThanSnapshot:
503 qCWarning(lspServerLog) << "Skipping update of current doc to obsolete version" << version
504 << "of document" << QString::fromUtf8(url);
505 return;
506 case VersionOk:
507 doc->snapshot.docVersion = version;
508 doc->snapshot.doc = item;
509 break;
510 }
511
512 if (!item.field(Fields::isValid).value().toBool(false)) {
513 qCWarning(lspServerLog) << "avoid update of validDoc to " << version << "of document"
514 << QString::fromUtf8(url) << "as it is invalid";
515 return;
516 }
517
518 switch (policy == ForceUpdate ? VersionOkForValidDocument
519 : checkVersionForValidDocument(*doc, version)) {
520 case VersionLowerThanValidSnapshot:
521 qCWarning(lspServerLog) << "Skipping update of valid doc to obsolete version" << version
522 << "of document" << QString::fromUtf8(url);
523 return;
525 doc->snapshot.validDocVersion = version;
526 doc->snapshot.validDoc = validItem;
527 break;
528 }
529}
530
531void QQmlCodeModel::newDocForOpenFile(const QByteArray &url, int version, const QString &docText,
532 QmlLsp::UpdatePolicy policy)
533{
534 qCDebug(codeModelLog) << "updating doc" << url << "to version" << version << "("
535 << docText.size() << "chars)";
536
537 const QString fPath = url2Path(url, UrlLookup::ForceLookup);
538 DomItem newCurrent = m_currentEnv.makeCopy(DomItem::CopyOption::EnvConnected).item();
539 const QStringList loadPaths = buildPathsForFileUrl(url) + importPathsForUrl(url);
540
541 if (std::shared_ptr<DomEnvironment> newCurrentPtr = newCurrent.ownerAs<DomEnvironment>()) {
542 newCurrentPtr->setLoadPaths(loadPaths);
543 }
544
545 // if the documentation root path is not set through the commandline,
546 // try to set it from the settings file (.qmlls.ini file)
547 if (documentationRootPath().isEmpty() && m_settings) {
548 // note: settings already searched current file in importPathsForFile() call above
549 const QString docDir = QStringLiteral(u"docDir");
550 if (m_settings->isSet(docDir))
551 setDocumentationRootPath(m_settings->value(docDir).toString());
552 }
553
554 Path p;
555 auto newCurrentPtr = newCurrent.ownerAs<DomEnvironment>();
556 newCurrentPtr->loadFile(FileToLoad::fromMemory(newCurrentPtr, fPath, docText),
557 [&p, this](Path, const DomItem &, const DomItem &newValue) {
558 const DomItem file = newValue.fileObject();
559 p = file.canonicalPath();
560 // Force population of the file by accessing isValid field. We
561 // don't want to populate the file after adding the file to the
562 // snapshot in updateItemInSnapshot.
563 file.field(Fields::isValid);
564 if (cmakeStatus() == HasCMake)
565 addFileWatches(file);
566 });
567 newCurrentPtr->loadPendingDependencies();
568 if (p) {
569 newCurrent.commitToBase(m_validEnv.ownerAs<DomEnvironment>());
570 QMutexLocker l(&m_mutex);
571 updateItemInSnapshot(m_currentEnv.path(p), m_validEnv.path(p), url, &m_openDocuments[url],
572 version, policy);
573 }
574 if (codeModelLog().isDebugEnabled()) {
575 qCDebug(codeModelLog) << "Finished update doc of " << url << "to version" << version;
576 snapshotByUrl(url).dump(qDebug() << "postSnapshot",
577 OpenDocumentSnapshot::DumpOption::AllCode);
578 }
579 // we should update the scope in the future thus call addOpen(url)
580 emit updatedSnapshot(url, policy);
581}
582
583void QQmlCodeModel::closeOpenFile(const QByteArray &url)
584{
585 QMutexLocker l(&m_mutex);
586 m_openDocuments.remove(url);
587}
588
590{
591 QMutexLocker l(&m_mutex);
592 return m_rootUrl;
593}
594
596{
597 QMutexLocker l(&m_mutex);
598 return m_buildPaths;
599}
600
602{
603 const QString fileName = url2Path(url);
604 QStringList result = importPaths();
605
606 const QString importPaths = u"importPaths"_s;
607 if (m_settings && m_settings->search(fileName, { QString(), verbose() }).isValid()
608 && m_settings->isSet(importPaths)) {
609 result.append(m_settings->value(importPaths).toString().split(QDir::listSeparator()));
610 }
611
612 const QStringList buildPath = buildPathsForFileUrl(url);
613 QMutexLocker l(&m_mutex);
614 m_buildInformation.loadSettingsFrom(buildPath);
615 result.append(m_buildInformation.importPathsFor(fileName));
616
617 return result;
618}
619
620void QQmlCodeModel::setImportPaths(const QStringList &importPaths)
621{
622 if (const auto &env = m_currentEnv.ownerAs<DomEnvironment>())
623 env->setLoadPaths(importPaths);
624 if (const auto &env = m_validEnv.ownerAs<DomEnvironment>())
625 env->setLoadPaths(importPaths);
626}
627
629{
630 if (const auto &env = m_currentEnv.ownerAs<DomEnvironment>())
631 return env->loadPaths();
632 if (const auto &env = m_validEnv.ownerAs<DomEnvironment>())
633 return env->loadPaths();
634 return {};
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}
690
691void QQmlCodeModel::openUpdate(const QByteArray &url, UpdatePolicy policy)
692{
693 std::optional<int> rNow = 0;
694 QString docText;
695
696 {
697 QMutexLocker l(&m_mutex);
698 OpenDocument &doc = m_openDocuments[url];
699 std::shared_ptr<Utils::TextDocument> document = doc.textDocument;
700 if (!document)
701 return;
702 {
703 QMutexLocker l2(document->mutex());
704 rNow = document->version();
705 }
706 if (!rNow
707 || (policy != ForceUpdate && doc.snapshot.docVersion
708 && *doc.snapshot.docVersion == *rNow)) {
709 return;
710 }
711
712 {
713 QMutexLocker l2(doc.textDocument->mutex());
714 rNow = doc.textDocument->version();
715 docText = doc.textDocument->toPlainText();
716 }
717 }
718 newDocForOpenFile(url, *rNow, docText, policy);
719}
720
721void QQmlCodeModel::addOpenToUpdate(const QByteArray &url, UpdatePolicy policy)
722{
723 {
724 QMutexLocker l(&m_mutex);
725 m_openDocumentsToUpdate[url] = policy;
726 }
727 openNeedUpdate();
728}
729
730QDebug OpenDocumentSnapshot::dump(QDebug dbg, DumpOptions options)
731{
732 dbg.noquote().nospace() << "{";
733 dbg << " url:" << QString::fromUtf8(url) << "\n";
734 dbg << " docVersion:" << (docVersion ? QString::number(*docVersion) : u"*none*"_s) << "\n";
735 if (options & DumpOption::LatestCode) {
736 dbg << " doc: ------------\n"
737 << doc.field(Fields::code).value().toString() << "\n==========\n";
738 } else {
739 dbg << u" doc:"
740 << (doc ? u"%1chars"_s.arg(doc.field(Fields::code).value().toString().size())
741 : u"*none*"_s)
742 << "\n";
743 }
744 dbg << " validDocVersion:"
745 << (validDocVersion ? QString::number(*validDocVersion) : u"*none*"_s) << "\n";
746 if (options & DumpOption::ValidCode) {
747 dbg << " validDoc: ------------\n"
748 << validDoc.field(Fields::code).value().toString() << "\n==========\n";
749 } else {
750 dbg << u" validDoc:"
751 << (validDoc ? u"%1chars"_s.arg(validDoc.field(Fields::code).value().toString().size())
752 : u"*none*"_s)
753 << "\n";
754 }
755 dbg << " scopeDependenciesLoadTime:" << scopeDependenciesLoadTime << "\n";
756 dbg << " scopeDependenciesChanged" << scopeDependenciesChanged << "\n";
757 dbg << "}";
758 return dbg;
759}
760
761void QQmllsBuildInformation::loadSettingsFrom(const QStringList &buildPaths)
762{
763#if QT_CONFIG(settings)
764 for (const QString &path : buildPaths) {
765 if (m_seenSettings.contains(path))
766 continue;
767 m_seenSettings.insert(path);
768
769 const QString iniPath = QString(path).append("/.qt/.qmlls.build.ini"_L1);
770 if (!QFile::exists(iniPath))
771 continue;
772
773 QSettings settings(iniPath, QSettings::IniFormat);
774 m_docDir = settings.value("docDir"_L1).toString();
775 for (const QString &group : settings.childGroups()) {
776 settings.beginGroup(group);
777
778 ModuleSetting moduleSetting;
779 moduleSetting.sourceFolder = group;
780 moduleSetting.sourceFolder.replace("<SLASH>"_L1, "/"_L1);
781 moduleSetting.importPaths = settings.value("importPaths"_L1)
782 .toString()
783 .split(QDir::listSeparator(), Qt::SkipEmptyParts);
784 m_moduleSettings.append(moduleSetting);
785 settings.endGroup();
786 }
787 }
788#else
789 Q_UNUSED(buildPaths);
790#endif
791}
792
794{
795 return settingFor(filePath).importPaths;
796}
797
799{
800 ModuleSetting result;
801 qsizetype longestMatch = 0;
802 for (const ModuleSetting &setting : m_moduleSettings) {
803 const qsizetype matchLength = setting.sourceFolder.size();
804 if (filePath.startsWith(setting.sourceFolder) && matchLength > longestMatch) {
805 result = setting;
806 longestMatch = matchLength;
807 }
808 }
809 return result;
810}
811
813
814} // namespace QmlLsp
815
816QT_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)
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)