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>
17#if QT_CONFIG(settings)
18# include <QtCore/qsettings.h>
21#include <QtQmlDom/private/qqmldomtop_p.h>
22#include <QtQmlCompiler/private/qqmljsutils_p.h>
31Q_STATIC_LOGGING_CATEGORY(
codeModelLog,
"qt.languageserver.codemodel")
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
91 QQmlToolingSharedSettings *settings)
105
106
107
111 if (cmakeStatus() == DoesNotHaveCMake)
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()) {
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);
132
133
134
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);
149 QMutexLocker l(&m_mutex);
150 m_openDocumentsToUpdate.clear();
152 shouldWait = m_nUpdateInProgress != 0;
156 QThread::yieldCurrentThread();
162 QObject::disconnect(&m_cppFileWatcher, &QFileSystemWatcher::fileChanged,
nullptr,
nullptr);
168 return openDocumentByUrl(url).snapshot;
173 const QString path = url2Path(url);
174 if (
auto validEnvPtr = m_validEnv.ownerAs<DomEnvironment>())
175 validEnvPtr->removePath(path);
176 if (
auto currentEnvPtr = m_currentEnv.ownerAs<DomEnvironment>())
177 currentEnvPtr->removePath(path);
180QString
QQmlCodeModel::url2Path(
const QByteArray &url, UrlLookup options)
184 QMutexLocker l(&m_mutex);
185 res = m_url2path.value(url);
187 if (!res.isEmpty() && options == UrlLookup::Caching)
189 QUrl qurl(QString::fromUtf8(url));
190 QFileInfo f(qurl.toLocalFile());
191 QString cPath = f.canonicalFilePath();
193 cPath = f.filePath();
195 QMutexLocker l(&m_mutex);
196 if (!res.isEmpty() && res != cPath)
197 m_path2url.remove(res);
198 m_url2path.insert(url, cPath);
199 m_path2url.insert(cPath, url);
207 QMutexLocker l(&m_mutex);
208 auto &openDoc = m_openDocuments[url];
209 if (!openDoc.textDocument)
210 openDoc.textDocument = std::make_shared<Utils::TextDocument>();
211 QMutexLocker l2(openDoc.textDocument->mutex());
212 openDoc.textDocument->setVersion(version);
213 openDoc.textDocument->setPlainText(docText);
215 addOpenToUpdate(url, NormalUpdate);
221 QMutexLocker l(&m_mutex);
222 return m_openDocuments.value(url);
227 QMutexLocker l(&m_mutex);
228 return m_openDocuments.isEmpty();
233 QMutexLocker l(&m_mutex);
239 QMutexLocker l(&m_mutex);
245 qCDebug(codeModelLog) <<
"openNeedUpdate";
246 const int maxThreads = 1;
248 QMutexLocker l(&m_mutex);
249 if (m_openDocumentsToUpdate.isEmpty() || m_nUpdateInProgress >= maxThreads
250 || m_state == State::Stopping) {
253 if (++m_nUpdateInProgress == 1)
256 QThreadPool::globalInstance()->start([
this]() {
257 QScopedValueRollback thread(m_openUpdateThread, QThread::currentThread());
258 while (openUpdateSome()) { }
259 emit openUpdateThreadFinished();
265 qCDebug(codeModelLog) <<
"openUpdateSome start";
269 QMutexLocker l(&m_mutex);
270 Q_ASSERT(QThread::currentThread() == m_openUpdateThread);
272 if (m_openDocumentsToUpdate.isEmpty()) {
273 if (--m_nUpdateInProgress == 0)
277 const auto it = m_openDocumentsToUpdate.begin();
280 m_openDocumentsToUpdate.erase(it);
282 bool hasMore =
false;
284 auto guard = qScopeGuard([
this, &hasMore]() {
285 QMutexLocker l(&m_mutex);
286 if (m_openDocumentsToUpdate.isEmpty()) {
287 if (--m_nUpdateInProgress == 0)
294 openUpdate(toUpdate, policy);
301 qCDebug(codeModelLog) <<
"openUpdateStart";
306 qCDebug(codeModelLog) <<
"openUpdateEnd";
313 std::vector<QByteArray> urls = { m_rootUrl };
315 QMutexLocker guard(&m_mutex);
316 for (
auto it = m_openDocuments.begin(), end = m_openDocuments.end(); it != end; ++it)
317 urls.push_back(it.key());
320 for (
const auto &url : urls)
321 result.append(buildPathsForFileUrl(url));
324 std::sort(result.begin(), result.end());
325 result.erase(std::unique(result.begin(), result.end()), result.end());
334 const auto result = settings->search(rootPath);
335 if (!result.isValid())
339 const QString valueString = settings->value(
"CMakeJobs"_L1).toString();
340 if (valueString == QQmlCodeModel::s_maxCMakeJobs)
341 return QThread::idealThreadCount();
343 const int cmakeJobs = settings->value(
"CMakeJobs"_L1).toInt(&ok);
344 if (!ok || cmakeJobs < 1)
349void QQmlCodeModel::callCMakeBuild(QProcessScheduler *scheduler)
351 const QStringList buildPaths = buildPathsForOpenedFiles();
352 const int cmakeJobs = cmakeJobsFromSettings(m_settings, url2Path(m_rootUrl), m_cmakeJobs);
354 QList<QProcessScheduler::Command> commands;
355 for (
const auto &path : buildPaths) {
356 if (!QFileInfo::exists(path + u"/.cmake"_s))
359 auto [program, arguments] = QQmlLSUtils::cmakeBuildCommand(path, cmakeJobs);
360 commands.append({ std::move(program), std::move(arguments) });
362 if (commands.isEmpty())
364 scheduler->schedule(commands, m_rootUrl);
367void QQmlCodeModel::onCMakeProcessFinished(
const QByteArray &id)
372 QMutexLocker guard(&m_mutex);
373 for (
auto it = m_openDocuments.begin(), end = m_openDocuments.end(); it != end; ++it)
374 m_openDocumentsToUpdate[it.key()] = ForceUpdate;
380
381
382
383
384
385
387 const QSet<QString> &ignoredFilePaths)
389 Q_ASSERT(!m_rootUrl.isEmpty());
390 QStringList fileNamesToSearch{ _fileNamesToSearch };
393 QMutexLocker guard(&m_mutex);
395 fileNamesToSearch.erase(std::remove_if(fileNamesToSearch.begin(), fileNamesToSearch.end(),
396 [
this](
const QString &fileName) {
397 return m_ignoreForWatching.contains(fileName);
399 fileNamesToSearch.end());
402 const QString rootDir = QUrl(QString::fromUtf8(m_rootUrl)).toLocalFile();
403 qCDebug(codeModelLog) <<
"Searching for files to watch in workspace folder" << rootDir;
405 const QStringList result =
406 QQmlLSUtils::findFilePathsFromFileNames(rootDir, fileNamesToSearch, ignoredFilePaths);
408 QMutexLocker guard(&m_mutex);
409 for (
const auto &fileName : fileNamesToSearch) {
410 if (std::none_of(result.begin(), result.end(),
411 [&fileName](
const QString &path) {
return path.endsWith(fileName); })) {
412 m_ignoreForWatching.insert(fileName);
419
420
421
422
425 const QmlFile *file = qmlFile.as<QmlFile>();
429 auto resolver = file->typeResolver();
433 auto types = resolver->importedTypes();
436 for (
const auto &type : types) {
440 const bool isComposite = type.scope.factory() || type.scope->isComposite();
444 const QString filePath = QFileInfo(type.scope->filePath()).fileName();
445 if (!filePath.isEmpty())
449 std::sort(result.begin(), result.end());
450 result.erase(std::unique(result.begin(), result.end()), result.end());
456
457
458
459
462 if (m_rootUrl.isEmpty())
464 const auto filesToWatch = fileNamesToWatch(qmlFile);
467 const QStringList alreadyWatchedFiles = m_cppFileWatcher.files();
468 const QSet<QString> alreadyWatchedFilesSet(alreadyWatchedFiles.begin(),
469 alreadyWatchedFiles.end());
470 QStringList filepathsToWatch = findFilePathsFromFileNames(filesToWatch, alreadyWatchedFilesSet);
472 if (filepathsToWatch.isEmpty())
475 QMutexLocker guard(&m_mutex);
476 const auto unwatchedPaths = m_cppFileWatcher.addPaths(filepathsToWatch);
477 if (!unwatchedPaths.isEmpty()) {
478 qCDebug(codeModelLog) <<
"Cannot watch paths" << unwatchedPaths <<
"from requested"
497 if (!doc.textDocument)
501 QMutexLocker guard2(doc.textDocument->mutex());
502 if (doc.textDocument->version() && *doc.textDocument->version() > version)
506 if (doc.snapshot.docVersion && *doc.snapshot.docVersion >= version)
515 if (doc.snapshot.validDocVersion && *doc.snapshot.validDocVersion >= version)
527 qCWarning(lspServerLog) <<
"Ignoring update to closed document" << QString::fromUtf8(url);
529 case VersionLowerThanDocument:
530 qCWarning(lspServerLog) <<
"Version" << version <<
"of document" << QString::fromUtf8(url)
531 <<
"is not the latest anymore";
533 case VersionLowerThanSnapshot:
534 qCWarning(lspServerLog) <<
"Skipping update of current doc to obsolete version" << version
535 <<
"of document" << QString::fromUtf8(url);
538 doc->snapshot.docVersion = version;
539 doc->snapshot.doc = item;
543 if (!item.field(Fields::isValid).value().toBool(
false)) {
544 qCWarning(lspServerLog) <<
"avoid update of validDoc to " << version <<
"of document"
545 << QString::fromUtf8(url) <<
"as it is invalid";
551 case VersionLowerThanValidSnapshot:
552 qCWarning(lspServerLog) <<
"Skipping update of valid doc to obsolete version" << version
553 <<
"of document" << QString::fromUtf8(url);
556 doc->snapshot.validDocVersion = version;
557 doc->snapshot.validDoc = validItem;
562void QQmlCodeModel::newDocForOpenFile(
const QByteArray &url,
int version,
const QString &docText,
565 Q_ASSERT(QThread::currentThread() == m_openUpdateThread);
566 qCDebug(codeModelLog) <<
"updating doc" << url <<
"to version" << version <<
"("
567 << docText.size() <<
"chars)";
569 const QString fPath = url2Path(url, UrlLookup::ForceLookup);
570 DomItem newCurrent = m_currentEnv.makeCopy(DomItem::CopyOption::EnvConnected).item();
574 if (documentationRootPath().isEmpty() && m_settings) {
576 const QString docDir = QStringLiteral(u"docDir");
577 if (m_settings->isSet(docDir))
578 setDocumentationRootPath(m_settings->value(docDir).toString());
582 auto newCurrentPtr = newCurrent.ownerAs<DomEnvironment>();
583 newCurrentPtr->setLoadPaths(importPathsForUrl(url));
584 newCurrentPtr->setResourceFiles(m_resourceFiles);
585 newCurrentPtr->loadFile(FileToLoad::fromMemory(newCurrentPtr, fPath, docText),
586 [&p,
this](Path,
const DomItem &,
const DomItem &newValue) {
587 const DomItem file = newValue.fileObject();
588 p = file.canonicalPath();
592 file.field(Fields::isValid);
593 if (cmakeStatus() == HasCMake)
594 addFileWatches(file);
596 newCurrentPtr->loadPendingDependencies();
598 newCurrent.commitToBase(m_validEnv.ownerAs<DomEnvironment>());
599 QMutexLocker l(&m_mutex);
600 updateItemInSnapshot(m_currentEnv.path(p), m_validEnv.path(p), url, &m_openDocuments[url],
603 if (codeModelLog().isDebugEnabled()) {
604 qCDebug(codeModelLog) <<
"Finished update doc of " << url <<
"to version" << version;
605 snapshotByUrl(url).dump(qDebug() <<
"postSnapshot",
606 OpenDocumentSnapshot::DumpOption::AllCode);
609 emit updatedSnapshot(url, policy);
614 QMutexLocker l(&m_mutex);
615 m_openDocuments.remove(url);
620 QMutexLocker l(&m_mutex);
626 QMutexLocker l(&m_mutex);
632 QStringList result = importPaths();
635 if (result.isEmpty() || result == QLibraryInfo::paths(QLibraryInfo::QmlImportsPath))
636 result << buildPathsForFileUrl(url);
638 const QString importPathsKey = u"importPaths"_s;
639 const QString fileName = url2Path(url);
640 if (m_settings && m_settings->search(fileName, { QString(), verbose() }).isValid()
641 && m_settings->isSet(importPathsKey)) {
642 result.append(m_settings->valueAsAbsolutePathList(importPathsKey, fileName));
649 QMutexLocker guard(&m_mutex);
650 m_importPaths = importPaths;
652 if (
const auto &env = m_currentEnv.ownerAs<DomEnvironment>())
653 env->setLoadPaths(importPaths);
654 if (
const auto &env = m_validEnv.ownerAs<DomEnvironment>())
655 env->setLoadPaths(importPaths);
660 QMutexLocker guard(&m_mutex);
661 return m_resourceFiles;
666 QMutexLocker guard(&m_mutex);
667 m_resourceFiles = resourceFiles;
669 if (
const auto &env = m_currentEnv.ownerAs<DomEnvironment>())
670 env->setResourceFiles(resourceFiles);
671 if (
const auto &env = m_validEnv.ownerAs<DomEnvironment>())
672 env->setResourceFiles(resourceFiles);
677 QMutexLocker guard(&m_mutex);
678 return m_importPaths;
685 std::reverse(buildPaths.begin(), buildPaths.end());
686 const int maxDeps = 4;
687 while (!buildPaths.isEmpty()) {
688 res += buildPaths.takeLast();
689 const QString &bPath = res.constLast();
690 const QString bPathExtended = bPath + u"/_deps";
691 if (QFile::exists(bPathExtended) && bPath.count(u"/_deps/"_s) < maxDeps) {
692 for (
const auto &fileInfo :
693 QDirListing{ bPathExtended, QDirListing::IteratorFlag::DirsOnly }) {
694 buildPaths.append(fileInfo.absoluteFilePath());
703 if (QStringList result = buildPaths(); !result.isEmpty())
704 return withDependentBuildDirectories(std::move(result));
707 if (!m_settings || !m_settings->search(url2Path(url), { QString(), verbose() }).isValid())
710 constexpr QLatin1String buildDir =
"buildDir"_L1;
711 if (!m_settings->isSet(buildDir))
714 const QString fileName = url2Path(url);
715 return withDependentBuildDirectories(m_settings->valueAsAbsolutePathList(buildDir, fileName));
721 QMutexLocker l(&m_mutex);
722 if (m_documentationRootPath == path)
724 m_documentationRootPath = path;
726 m_helpManager.setDocumentationRootPath(path);
731 QMutexLocker l(&m_mutex);
732 m_buildPaths = paths;
737 std::optional<
int> rNow = 0;
741 QMutexLocker l(&m_mutex);
742 Q_ASSERT(QThread::currentThread() == m_openUpdateThread);
744 std::shared_ptr<Utils::TextDocument> document = doc.textDocument;
748 QMutexLocker l2(document->mutex());
749 rNow = document->version();
752 || (policy != ForceUpdate && doc.snapshot.docVersion
753 && *doc.snapshot.docVersion == *rNow)) {
758 QMutexLocker l2(doc.textDocument->mutex());
759 rNow = doc.textDocument->version();
760 docText = doc.textDocument->toPlainText();
763 newDocForOpenFile(url, *rNow, docText, policy);
769 QMutexLocker l(&m_mutex);
770 m_openDocumentsToUpdate[url] = policy;
777 dbg.noquote().nospace() <<
"{";
778 dbg <<
" url:" << QString::fromUtf8(url) <<
"\n";
779 dbg <<
" docVersion:" << (docVersion ? QString::number(*docVersion) : u"*none*"_s) <<
"\n";
781 dbg <<
" doc: ------------\n"
782 << doc.field(Fields::code).value().toString() <<
"\n==========\n";
785 << (doc ? u"%1chars"_s.arg(doc.field(Fields::code).value().toString().size())
789 dbg <<
" validDocVersion:"
790 << (validDocVersion ? QString::number(*validDocVersion) : u"*none*"_s) <<
"\n";
792 dbg <<
" validDoc: ------------\n"
793 << validDoc.field(Fields::code).value().toString() <<
"\n==========\n";
796 << (validDoc ? u"%1chars"_s.arg(validDoc.field(Fields::code).value().toString().size())
800 dbg <<
" scopeDependenciesLoadTime:" << scopeDependenciesLoadTime <<
"\n";
810 return &moduleSettings->emplaceBack();
812 auto it = std::find_if(
813 moduleSettings->begin(), moduleSettings->end(),
814 [&sourceFolder](
const ModuleSetting s) {
return s.sourceFolder == sourceFolder; });
815 if (it == moduleSettings->end())
816 return &moduleSettings->emplaceBack();
822#if QT_CONFIG(settings)
823 for (
const QString &path : buildPaths) {
824 if (policy != ForceUpdate && m_seenSettings.contains(path))
826 m_seenSettings.insert(path);
828 const QString iniPath = QString(path).append(
"/.qt/.qmlls.build.ini"_L1);
829 if (!QFile::exists(iniPath))
832 QSettings settings(iniPath, QSettings::IniFormat);
833 m_docDir = settings.value(
"docDir"_L1).toString();
834 const qsizetype version = settings.value(
"version"_L1,
"1"_L1).toString().toInt();
837 const int entries = settings.beginReadArray(
"workspaces"_L1);
838 for (
int i = 0; i < entries; ++i) {
839 settings.setArrayIndex(i);
841 const QString sourceFolder = settings.value(
"sourcePath").toString();
842 ModuleSetting *moduleSetting =
843 moduleSettingFor(sourceFolder, &m_moduleSettings, policy);
844 moduleSetting->sourceFolder = sourceFolder;
845 moduleSetting->importPaths =
846 settings.value(
"importPaths"_L1)
848 .split(QDir::listSeparator(), Qt::SkipEmptyParts);
849 moduleSetting->resourceFiles =
850 settings.value(
"resourceFiles"_L1)
852 .split(QDir::listSeparator(), Qt::SkipEmptyParts);
859 for (
const QString &group : settings.childGroups()) {
860 settings.beginGroup(group);
862 const QString sourceFolder = QString(group).replace(
"<SLASH>"_L1,
"/"_L1);
863 ModuleSetting *moduleSetting =
864 moduleSettingFor(sourceFolder, &m_moduleSettings, policy);
865 moduleSetting->sourceFolder = sourceFolder;
866 moduleSetting->importPaths =
867 settings.value(
"importPaths"_L1)
869 .split(QDir::listSeparator(), Qt::SkipEmptyParts);
870 moduleSetting->resourceFiles =
871 settings.value(
"resourceFiles"_L1)
873 .split(QDir::listSeparator(), Qt::SkipEmptyParts);
881 Q_UNUSED(buildPaths);
887 return settingFor(filePath).importPaths;
892 return settingFor(filePath).resourceFiles;
898 qsizetype longestMatch = 0;
899 for (
const ModuleSetting &setting : m_moduleSettings) {
900 const qsizetype matchLength = setting.sourceFolder.size();
901 if (filePath.startsWith(setting.sourceFolder) && matchLength > longestMatch) {
903 longestMatch = matchLength;
906 QQmlToolingSettings::resolveRelativeImportPaths(filePath, &result.importPaths);
912 m_moduleSettings.append(moduleSetting);
917#if QT_CONFIG(settings)
918 QSettings settings(file, QSettings::IniFormat);
919 settings.setValue(
"docDir"_L1, m_docDir);
920 settings.setValue(
"version"_L1,
"2"_L1);
921 settings.beginWriteArray(
"workspaces"_L1, m_moduleSettings.size());
922 for (
int i = 0; i < m_moduleSettings.size(); ++i) {
923 settings.setArrayIndex(i);
924 settings.setValue(
"sourcePath"_L1, m_moduleSettings[i].sourceFolder);
925 settings.setValue(
"importPaths"_L1,
926 m_moduleSettings[i].importPaths.join(QDir::listSeparator()));
927 settings.setValue(
"resourceFiles"_L1,
928 m_moduleSettings[i].resourceFiles.join(QDir::listSeparator()));
bool scopeDependenciesChanged
QDebug dump(QDebug dbg, DumpOptions dump=DumpOption::NoCode)
void newOpenFile(const QByteArray &url, int version, const QString &docText)
void addOpenToUpdate(const QByteArray &, UpdatePolicy policy)
OpenDocumentSnapshot snapshotByUrl(const QByteArray &url)
void tryEnableCMakeCalls(QProcessScheduler *scheduler)
void prepareForShutdown()
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 resourceFiles() const
QStringList buildPathsForOpenedFiles()
void setBuildPaths(const QStringList &paths)
void closeOpenFile(const QByteArray &url)
RegisteredSemanticTokens & registeredTokens()
void removeDirectory(const QByteArray &)
void setImportPaths(const QStringList &paths)
void setResourceFiles(const QStringList &resourceFiles)
Combined button and popup list for selecting options.
static ModuleSetting * moduleSettingFor(const QString &sourceFolder, ModuleSettings *moduleSettings, UpdatePolicy policy)
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)
static int cmakeJobsFromSettings(QQmlToolingSharedSettings *settings, const QString &rootPath, int defaultValue)