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(&m_cppFileWatcher, &QFileSystemWatcher::fileChanged, scheduler,
124 [
this, scheduler] { callCMakeBuild(scheduler); });
125 setCMakeStatus(HasCMake);
126 callCMakeBuild(scheduler);
130
131
132
135 QMutexLocker guard(&m_mutex);
136 m_cmakeStatus = DoesNotHaveCMake;
137 if (
const QStringList toRemove = m_cppFileWatcher.files(); !toRemove.isEmpty())
138 m_cppFileWatcher.removePaths(toRemove);
139 QObject::disconnect(&m_cppFileWatcher, &QFileSystemWatcher::fileChanged,
nullptr,
nullptr);
147 QMutexLocker l(&m_mutex);
148 m_openDocumentsToUpdate.clear();
150 shouldWait = m_nUpdateInProgress != 0;
154 QThread::yieldCurrentThread();
160 QObject::disconnect(&m_cppFileWatcher, &QFileSystemWatcher::fileChanged,
nullptr,
nullptr);
166 return openDocumentByUrl(url).snapshot;
171 const QString path = url2Path(url);
172 if (
auto validEnvPtr = m_validEnv.ownerAs<DomEnvironment>())
173 validEnvPtr->removePath(path);
174 if (
auto currentEnvPtr = m_currentEnv.ownerAs<DomEnvironment>())
175 currentEnvPtr->removePath(path);
178QString
QQmlCodeModel::url2Path(
const QByteArray &url, UrlLookup options)
182 QMutexLocker l(&m_mutex);
183 res = m_url2path.value(url);
185 if (!res.isEmpty() && options == UrlLookup::Caching)
187 QUrl qurl(QString::fromUtf8(url));
188 QFileInfo f(qurl.toLocalFile());
189 QString cPath = f.canonicalFilePath();
191 cPath = f.filePath();
193 QMutexLocker l(&m_mutex);
194 if (!res.isEmpty() && res != cPath)
195 m_path2url.remove(res);
196 m_url2path.insert(url, cPath);
197 m_path2url.insert(cPath, url);
205 QMutexLocker l(&m_mutex);
206 auto &openDoc = m_openDocuments[url];
207 if (!openDoc.textDocument)
208 openDoc.textDocument = std::make_shared<Utils::TextDocument>();
209 QMutexLocker l2(openDoc.textDocument->mutex());
210 openDoc.textDocument->setVersion(version);
211 openDoc.textDocument->setPlainText(docText);
213 addOpenToUpdate(url, NormalUpdate);
219 QMutexLocker l(&m_mutex);
220 return m_openDocuments.value(url);
225 QMutexLocker l(&m_mutex);
226 return m_openDocuments.isEmpty();
231 QMutexLocker l(&m_mutex);
237 QMutexLocker l(&m_mutex);
243 qCDebug(codeModelLog) <<
"openNeedUpdate";
244 const int maxThreads = 1;
246 QMutexLocker l(&m_mutex);
247 if (m_openDocumentsToUpdate.isEmpty() || m_nUpdateInProgress >= maxThreads
248 || m_state == State::Stopping) {
251 if (++m_nUpdateInProgress == 1)
254 QThreadPool::globalInstance()->start([
this]() {
255 QScopedValueRollback thread(m_openUpdateThread, QThread::currentThread());
256 while (openUpdateSome()) { }
257 emit openUpdateThreadFinished();
263 qCDebug(codeModelLog) <<
"openUpdateSome start";
267 QMutexLocker l(&m_mutex);
268 Q_ASSERT(QThread::currentThread() == m_openUpdateThread);
270 if (m_openDocumentsToUpdate.isEmpty()) {
271 if (--m_nUpdateInProgress == 0)
275 const auto it = m_openDocumentsToUpdate.begin();
278 m_openDocumentsToUpdate.erase(it);
280 bool hasMore =
false;
282 auto guard = qScopeGuard([
this, &hasMore]() {
283 QMutexLocker l(&m_mutex);
284 if (m_openDocumentsToUpdate.isEmpty()) {
285 if (--m_nUpdateInProgress == 0)
292 openUpdate(toUpdate, policy);
299 qCDebug(codeModelLog) <<
"openUpdateStart";
304 qCDebug(codeModelLog) <<
"openUpdateEnd";
311 std::vector<QByteArray> urls = { m_rootUrl };
313 QMutexLocker guard(&m_mutex);
314 for (
auto it = m_openDocuments.begin(), end = m_openDocuments.end(); it != end; ++it)
315 urls.push_back(it.key());
318 for (
const auto &url : urls)
319 result.append(buildPathsForFileUrl(url));
322 std::sort(result.begin(), result.end());
323 result.erase(std::unique(result.begin(), result.end()), result.end());
332 const auto result = settings->search(rootPath);
333 if (!result.isValid())
337 const QString valueString = settings->value(
"CMakeJobs"_L1).toString();
338 if (valueString == QQmlCodeModel::s_maxCMakeJobs)
339 return QThread::idealThreadCount();
341 const int cmakeJobs = settings->value(
"CMakeJobs"_L1).toInt(&ok);
342 if (!ok || cmakeJobs < 1)
347void QQmlCodeModel::callCMakeBuild(QProcessScheduler *scheduler)
349 const QStringList buildPaths = buildPathsForOpenedFiles();
350 const int cmakeJobs = cmakeJobsFromSettings(m_settings, url2Path(m_rootUrl), m_cmakeJobs);
352 QList<QProcessScheduler::Command> commands;
353 for (
const auto &path : buildPaths) {
354 if (!QFileInfo::exists(path + u"/.cmake"_s))
357 auto [program, arguments] = QQmlLSUtils::cmakeBuildCommand(path, cmakeJobs);
358 commands.append({ std::move(program), std::move(arguments) });
360 if (commands.isEmpty())
362 qCInfo(codeModelLog) <<
"Starting background build on paths" << buildPaths.join(
", "_L1);
363 scheduler->schedule(commands, m_rootUrl.isEmpty() ?
"Default workspace"_ba : m_rootUrl);
368 QMutexLocker guard(&m_mutex);
369 for (
auto it = m_openDocuments.begin(), end = m_openDocuments.end(); it != end; ++it)
370 m_openDocumentsToUpdate[it.key()] = ForceUpdate;
376
377
378
379
380
381
383 const QSet<QString> &ignoredFilePaths)
385 Q_ASSERT(!m_rootUrl.isEmpty());
386 QStringList fileNamesToSearch{ _fileNamesToSearch };
389 QMutexLocker guard(&m_mutex);
391 fileNamesToSearch.erase(std::remove_if(fileNamesToSearch.begin(), fileNamesToSearch.end(),
392 [
this](
const QString &fileName) {
393 return m_ignoreForWatching.contains(fileName);
395 fileNamesToSearch.end());
398 const QString rootDir = QUrl(QString::fromUtf8(m_rootUrl)).toLocalFile();
399 qCDebug(codeModelLog) <<
"Searching for files to watch in workspace folder" << rootDir;
401 const QStringList result =
402 QQmlLSUtils::findFilePathsFromFileNames(rootDir, fileNamesToSearch, ignoredFilePaths);
404 QMutexLocker guard(&m_mutex);
405 for (
const auto &fileName : fileNamesToSearch) {
406 if (std::none_of(result.begin(), result.end(),
407 [&fileName](
const QString &path) {
return path.endsWith(fileName); })) {
408 m_ignoreForWatching.insert(fileName);
415
416
417
418
421 const QmlFile *file = qmlFile.as<QmlFile>();
425 auto resolver = file->typeResolver();
429 auto types = resolver->importedTypes();
432 for (
const auto &type : types) {
436 const bool isComposite = type.scope.factory() || type.scope->isComposite();
440 const QString filePath = QFileInfo(type.scope->filePath()).fileName();
441 if (!filePath.isEmpty())
445 std::sort(result.begin(), result.end());
446 result.erase(std::unique(result.begin(), result.end()), result.end());
452
453
454
455
458 if (m_rootUrl.isEmpty())
460 const auto filesToWatch = fileNamesToWatch(qmlFile);
463 const QStringList alreadyWatchedFiles = m_cppFileWatcher.files();
464 const QSet<QString> alreadyWatchedFilesSet(alreadyWatchedFiles.begin(),
465 alreadyWatchedFiles.end());
466 QStringList filepathsToWatch = findFilePathsFromFileNames(filesToWatch, alreadyWatchedFilesSet);
468 if (filepathsToWatch.isEmpty())
471 QMutexLocker guard(&m_mutex);
472 const auto unwatchedPaths = m_cppFileWatcher.addPaths(filepathsToWatch);
473 if (!unwatchedPaths.isEmpty()) {
474 qCDebug(codeModelLog) <<
"Cannot watch paths" << unwatchedPaths <<
"from requested"
493 if (!doc.textDocument)
497 QMutexLocker guard2(doc.textDocument->mutex());
498 if (doc.textDocument->version() && *doc.textDocument->version() > version)
502 if (doc.snapshot.docVersion && *doc.snapshot.docVersion >= version)
511 if (doc.snapshot.validDocVersion && *doc.snapshot.validDocVersion >= version)
523 qCWarning(lspServerLog) <<
"Ignoring update to closed document" << QString::fromUtf8(url);
525 case VersionLowerThanDocument:
526 qCWarning(lspServerLog) <<
"Version" << version <<
"of document" << QString::fromUtf8(url)
527 <<
"is not the latest anymore";
529 case VersionLowerThanSnapshot:
530 qCWarning(lspServerLog) <<
"Skipping update of current doc to obsolete version" << version
531 <<
"of document" << QString::fromUtf8(url);
534 doc->snapshot.docVersion = version;
535 doc->snapshot.doc = item;
539 if (!item.field(Fields::isValid).value().toBool(
false)) {
540 qCWarning(lspServerLog) <<
"avoid update of validDoc to " << version <<
"of document"
541 << QString::fromUtf8(url) <<
"as it is invalid";
547 case VersionLowerThanValidSnapshot:
548 qCWarning(lspServerLog) <<
"Skipping update of valid doc to obsolete version" << version
549 <<
"of document" << QString::fromUtf8(url);
552 doc->snapshot.validDocVersion = version;
553 doc->snapshot.validDoc = validItem;
558void QQmlCodeModel::newDocForOpenFile(
const QByteArray &url,
int version,
const QString &docText,
561 Q_ASSERT(QThread::currentThread() == m_openUpdateThread);
562 qCDebug(codeModelLog) <<
"updating doc" << url <<
"to version" << version <<
"("
563 << docText.size() <<
"chars)";
565 const QString fPath = url2Path(url, UrlLookup::ForceLookup);
566 DomItem newCurrent = m_currentEnv.makeCopy(DomItem::CopyOption::EnvConnected).item();
570 if (documentationRootPath().isEmpty() && m_settings) {
572 const QString docDir = QStringLiteral(u"docDir");
573 if (m_settings->isSet(docDir))
574 setDocumentationRootPath(m_settings->value(docDir).toString());
578 auto newCurrentPtr = newCurrent.ownerAs<DomEnvironment>();
579 newCurrentPtr->setLoadPaths(importPathsForUrl(url));
580 newCurrentPtr->setResourceFiles(m_resourceFiles);
581 newCurrentPtr->loadFile(FileToLoad::fromMemory(newCurrentPtr, fPath, docText),
582 [&p,
this](Path,
const DomItem &,
const DomItem &newValue) {
583 const DomItem file = newValue.fileObject();
584 p = file.canonicalPath();
588 file.field(Fields::isValid);
589 if (cmakeStatus() == HasCMake)
590 addFileWatches(file);
592 newCurrentPtr->loadPendingDependencies();
594 newCurrent.commitToBase(m_validEnv.ownerAs<DomEnvironment>());
595 QMutexLocker l(&m_mutex);
596 updateItemInSnapshot(m_currentEnv.path(p), m_validEnv.path(p), url, &m_openDocuments[url],
599 if (codeModelLog().isDebugEnabled()) {
600 qCDebug(codeModelLog) <<
"Finished update doc of " << url <<
"to version" << version;
601 snapshotByUrl(url).dump(qDebug() <<
"postSnapshot",
602 OpenDocumentSnapshot::DumpOption::AllCode);
605 emit updatedSnapshot(url, policy);
610 QMutexLocker l(&m_mutex);
611 m_openDocuments.remove(url);
616 QMutexLocker l(&m_mutex);
622 QMutexLocker l(&m_mutex);
628 QStringList result = importPaths();
631 if (result.isEmpty() || result == QLibraryInfo::paths(QLibraryInfo::QmlImportsPath))
632 result << buildPathsForFileUrl(url);
634 const QString importPathsKey = u"importPaths"_s;
635 const QString fileName = url2Path(url);
636 if (m_settings && m_settings->search(fileName, { QString(), verbose() }).isValid()
637 && m_settings->isSet(importPathsKey)) {
638 result.append(m_settings->valueAsAbsolutePathList(importPathsKey, fileName));
645 QMutexLocker guard(&m_mutex);
646 m_importPaths = importPaths;
648 if (
const auto &env = m_currentEnv.ownerAs<DomEnvironment>())
649 env->setLoadPaths(importPaths);
650 if (
const auto &env = m_validEnv.ownerAs<DomEnvironment>())
651 env->setLoadPaths(importPaths);
656 QMutexLocker guard(&m_mutex);
657 return m_resourceFiles;
662 QMutexLocker guard(&m_mutex);
663 m_resourceFiles = resourceFiles;
665 if (
const auto &env = m_currentEnv.ownerAs<DomEnvironment>())
666 env->setResourceFiles(resourceFiles);
667 if (
const auto &env = m_validEnv.ownerAs<DomEnvironment>())
668 env->setResourceFiles(resourceFiles);
673 QMutexLocker guard(&m_mutex);
674 return m_importPaths;
681 std::reverse(buildPaths.begin(), buildPaths.end());
682 const int maxDeps = 4;
683 while (!buildPaths.isEmpty()) {
684 res += buildPaths.takeLast();
685 const QString &bPath = res.constLast();
686 const QString bPathExtended = bPath + u"/_deps";
687 if (QFile::exists(bPathExtended) && bPath.count(u"/_deps/"_s) < maxDeps) {
688 for (
const auto &fileInfo :
689 QDirListing{ bPathExtended, QDirListing::IteratorFlag::DirsOnly }) {
690 buildPaths.append(fileInfo.absoluteFilePath());
699 if (QStringList result = buildPaths(); !result.isEmpty())
700 return withDependentBuildDirectories(std::move(result));
703 if (!m_settings || !m_settings->search(url2Path(url), { QString(), verbose() }).isValid())
706 constexpr QLatin1String buildDir =
"buildDir"_L1;
707 if (!m_settings->isSet(buildDir))
710 const QString fileName = url2Path(url);
711 return withDependentBuildDirectories(m_settings->valueAsAbsolutePathList(buildDir, fileName));
717 QMutexLocker l(&m_mutex);
718 if (m_documentationRootPath == path)
720 m_documentationRootPath = path;
722 m_helpManager.setDocumentationRootPath(path);
727 QMutexLocker l(&m_mutex);
728 m_buildPaths = paths;
733 std::optional<
int> rNow = 0;
737 QMutexLocker l(&m_mutex);
738 Q_ASSERT(QThread::currentThread() == m_openUpdateThread);
740 std::shared_ptr<Utils::TextDocument> document = doc.textDocument;
744 QMutexLocker l2(document->mutex());
745 rNow = document->version();
748 || (policy != ForceUpdate && doc.snapshot.docVersion
749 && *doc.snapshot.docVersion == *rNow)) {
754 QMutexLocker l2(doc.textDocument->mutex());
755 rNow = doc.textDocument->version();
756 docText = doc.textDocument->toPlainText();
759 newDocForOpenFile(url, *rNow, docText, policy);
765 QMutexLocker l(&m_mutex);
766 m_openDocumentsToUpdate[url] = policy;
773 dbg.noquote().nospace() <<
"{";
774 dbg <<
" url:" << QString::fromUtf8(url) <<
"\n";
775 dbg <<
" docVersion:" << (docVersion ? QString::number(*docVersion) : u"*none*"_s) <<
"\n";
777 dbg <<
" doc: ------------\n"
778 << doc.field(Fields::code).value().toString() <<
"\n==========\n";
781 << (doc ? u"%1chars"_s.arg(doc.field(Fields::code).value().toString().size())
785 dbg <<
" validDocVersion:"
786 << (validDocVersion ? QString::number(*validDocVersion) : u"*none*"_s) <<
"\n";
788 dbg <<
" validDoc: ------------\n"
789 << validDoc.field(Fields::code).value().toString() <<
"\n==========\n";
792 << (validDoc ? u"%1chars"_s.arg(validDoc.field(Fields::code).value().toString().size())
796 dbg <<
" scopeDependenciesLoadTime:" << scopeDependenciesLoadTime <<
"\n";
806 return &moduleSettings->emplaceBack(
ModuleSetting {sourceFolder, {}, {}});
808 auto it = std::find_if(
809 moduleSettings->begin(), moduleSettings->end(),
810 [&sourceFolder](
const ModuleSetting s) {
return s.sourceFolder == sourceFolder; });
811 if (it == moduleSettings->end())
812 return &moduleSettings->emplaceBack(
ModuleSetting {sourceFolder, {}, {}});
818 return variant.toString().split(QDir::listSeparator(), Qt::SkipEmptyParts);
825 static constexpr QLatin1String sourcePathKey =
"workspaces/0/sourcePath"_L1;
826 if (!settings->contains(sourcePathKey))
830 moduleSettingFor(settings->value(sourcePathKey).toString(), moduleSettings, policy);
831 moduleSetting->importPaths = asStringList(settings->value(
"workspaces/0/importPaths"_L1));
832 moduleSetting->resourceFiles = asStringList(settings->value(
"workspaces/0/resourceFiles"_L1));
838 handleArrayStartingAt0(settings, moduleSettings, policy);
840 const int entries = settings->beginReadArray(
"workspaces"_L1);
841 for (
int i = 0; i < entries; ++i) {
842 settings->setArrayIndex(i);
844 if (!settings->contains(
"sourcePath"_L1))
848 moduleSettingFor(settings->value(
"sourcePath").toString(), moduleSettings, policy);
849 moduleSetting->importPaths = asStringList(settings->value(
"importPaths"_L1));
850 moduleSetting->resourceFiles = asStringList(settings->value(
"resourceFiles"_L1));
852 settings->endArray();
857#if QT_CONFIG(settings)
858 for (
const QString &path : buildPaths) {
859 if (policy != ForceUpdate && m_seenSettings.contains(path))
861 m_seenSettings.insert(path);
863 const QString iniPath = QString(path).append(
"/.qt/.qmlls.build.ini"_L1);
864 if (!QFile::exists(iniPath))
867 QSettings settings(iniPath, QSettings::IniFormat);
868 m_docDir = settings.value(
"docDir"_L1).toString();
869 const qsizetype version = settings.value(
"version"_L1,
"1"_L1).toString().toInt();
872 loadWorkspacesV2(&settings, &m_moduleSettings, policy);
877 for (
const QString &group : settings.childGroups()) {
878 settings.beginGroup(group);
880 ModuleSetting *moduleSetting = moduleSettingFor(
881 QString(group).replace(
"<SLASH>"_L1,
"/"_L1), &m_moduleSettings, policy);
882 moduleSetting->importPaths = asStringList(settings.value(
"importPaths"_L1));
883 moduleSetting->resourceFiles = asStringList(settings.value(
"resourceFiles"_L1));
891 Q_UNUSED(buildPaths);
897 return settingFor(filePath).importPaths;
902 return settingFor(filePath).resourceFiles;
908 qsizetype longestMatch = 0;
909 for (
const ModuleSetting &setting : m_moduleSettings) {
910 const qsizetype matchLength = setting.sourceFolder.size();
911 if (filePath.startsWith(setting.sourceFolder) && matchLength > longestMatch) {
913 longestMatch = matchLength;
916 QQmlToolingSettings::resolveRelativeImportPaths(filePath, &result.importPaths);
922 m_moduleSettings.append(moduleSetting);
927#if QT_CONFIG(settings)
928 QSettings settings(file, QSettings::IniFormat);
929 settings.setValue(
"docDir"_L1, m_docDir);
930 settings.setValue(
"version"_L1,
"2"_L1);
931 settings.beginWriteArray(
"workspaces"_L1, m_moduleSettings.size());
932 for (
int i = 0; i < m_moduleSettings.size(); ++i) {
933 settings.setArrayIndex(i);
934 settings.setValue(
"sourcePath"_L1, m_moduleSettings[i].sourceFolder);
935 settings.setValue(
"importPaths"_L1,
936 m_moduleSettings[i].importPaths.join(QDir::listSeparator()));
937 settings.setValue(
"resourceFiles"_L1,
938 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 reloadAllOpenFiles()
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 QStringList asStringList(const QVariant &variant)
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)
static void loadWorkspacesV2(QSettings *settings, ModuleSettings *moduleSettings, UpdatePolicy policy)
@ VersionLowerThanSnapshot
@ VersionLowerThanDocument
static void handleArrayStartingAt0(QSettings *settings, ModuleSettings *moduleSettings, UpdatePolicy policy)
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)