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 scheduler->schedule(commands, m_rootUrl);
367 QMutexLocker guard(&m_mutex);
368 for (
auto it = m_openDocuments.begin(), end = m_openDocuments.end(); it != end; ++it)
369 m_openDocumentsToUpdate[it.key()] = ForceUpdate;
375
376
377
378
379
380
382 const QSet<QString> &ignoredFilePaths)
384 Q_ASSERT(!m_rootUrl.isEmpty());
385 QStringList fileNamesToSearch{ _fileNamesToSearch };
388 QMutexLocker guard(&m_mutex);
390 fileNamesToSearch.erase(std::remove_if(fileNamesToSearch.begin(), fileNamesToSearch.end(),
391 [
this](
const QString &fileName) {
392 return m_ignoreForWatching.contains(fileName);
394 fileNamesToSearch.end());
397 const QString rootDir = QUrl(QString::fromUtf8(m_rootUrl)).toLocalFile();
398 qCDebug(codeModelLog) <<
"Searching for files to watch in workspace folder" << rootDir;
400 const QStringList result =
401 QQmlLSUtils::findFilePathsFromFileNames(rootDir, fileNamesToSearch, ignoredFilePaths);
403 QMutexLocker guard(&m_mutex);
404 for (
const auto &fileName : fileNamesToSearch) {
405 if (std::none_of(result.begin(), result.end(),
406 [&fileName](
const QString &path) {
return path.endsWith(fileName); })) {
407 m_ignoreForWatching.insert(fileName);
414
415
416
417
420 const QmlFile *file = qmlFile.as<QmlFile>();
424 auto resolver = file->typeResolver();
428 auto types = resolver->importedTypes();
431 for (
const auto &type : types) {
435 const bool isComposite = type.scope.factory() || type.scope->isComposite();
439 const QString filePath = QFileInfo(type.scope->filePath()).fileName();
440 if (!filePath.isEmpty())
444 std::sort(result.begin(), result.end());
445 result.erase(std::unique(result.begin(), result.end()), result.end());
451
452
453
454
457 if (m_rootUrl.isEmpty())
459 const auto filesToWatch = fileNamesToWatch(qmlFile);
462 const QStringList alreadyWatchedFiles = m_cppFileWatcher.files();
463 const QSet<QString> alreadyWatchedFilesSet(alreadyWatchedFiles.begin(),
464 alreadyWatchedFiles.end());
465 QStringList filepathsToWatch = findFilePathsFromFileNames(filesToWatch, alreadyWatchedFilesSet);
467 if (filepathsToWatch.isEmpty())
470 QMutexLocker guard(&m_mutex);
471 const auto unwatchedPaths = m_cppFileWatcher.addPaths(filepathsToWatch);
472 if (!unwatchedPaths.isEmpty()) {
473 qCDebug(codeModelLog) <<
"Cannot watch paths" << unwatchedPaths <<
"from requested"
492 if (!doc.textDocument)
496 QMutexLocker guard2(doc.textDocument->mutex());
497 if (doc.textDocument->version() && *doc.textDocument->version() > version)
501 if (doc.snapshot.docVersion && *doc.snapshot.docVersion >= version)
510 if (doc.snapshot.validDocVersion && *doc.snapshot.validDocVersion >= version)
522 qCWarning(lspServerLog) <<
"Ignoring update to closed document" << QString::fromUtf8(url);
524 case VersionLowerThanDocument:
525 qCWarning(lspServerLog) <<
"Version" << version <<
"of document" << QString::fromUtf8(url)
526 <<
"is not the latest anymore";
528 case VersionLowerThanSnapshot:
529 qCWarning(lspServerLog) <<
"Skipping update of current doc to obsolete version" << version
530 <<
"of document" << QString::fromUtf8(url);
533 doc->snapshot.docVersion = version;
534 doc->snapshot.doc = item;
538 if (!item.field(Fields::isValid).value().toBool(
false)) {
539 qCWarning(lspServerLog) <<
"avoid update of validDoc to " << version <<
"of document"
540 << QString::fromUtf8(url) <<
"as it is invalid";
546 case VersionLowerThanValidSnapshot:
547 qCWarning(lspServerLog) <<
"Skipping update of valid doc to obsolete version" << version
548 <<
"of document" << QString::fromUtf8(url);
551 doc->snapshot.validDocVersion = version;
552 doc->snapshot.validDoc = validItem;
557void QQmlCodeModel::newDocForOpenFile(
const QByteArray &url,
int version,
const QString &docText,
560 Q_ASSERT(QThread::currentThread() == m_openUpdateThread);
561 qCDebug(codeModelLog) <<
"updating doc" << url <<
"to version" << version <<
"("
562 << docText.size() <<
"chars)";
564 const QString fPath = url2Path(url, UrlLookup::ForceLookup);
565 DomItem newCurrent = m_currentEnv.makeCopy(DomItem::CopyOption::EnvConnected).item();
569 if (documentationRootPath().isEmpty() && m_settings) {
571 const QString docDir = QStringLiteral(u"docDir");
572 if (m_settings->isSet(docDir))
573 setDocumentationRootPath(m_settings->value(docDir).toString());
577 auto newCurrentPtr = newCurrent.ownerAs<DomEnvironment>();
578 newCurrentPtr->setLoadPaths(importPathsForUrl(url));
579 newCurrentPtr->setResourceFiles(m_resourceFiles);
580 newCurrentPtr->loadFile(FileToLoad::fromMemory(newCurrentPtr, fPath, docText),
581 [&p,
this](Path,
const DomItem &,
const DomItem &newValue) {
582 const DomItem file = newValue.fileObject();
583 p = file.canonicalPath();
587 file.field(Fields::isValid);
588 if (cmakeStatus() == HasCMake)
589 addFileWatches(file);
591 newCurrentPtr->loadPendingDependencies();
593 newCurrent.commitToBase(m_validEnv.ownerAs<DomEnvironment>());
594 QMutexLocker l(&m_mutex);
595 updateItemInSnapshot(m_currentEnv.path(p), m_validEnv.path(p), url, &m_openDocuments[url],
598 if (codeModelLog().isDebugEnabled()) {
599 qCDebug(codeModelLog) <<
"Finished update doc of " << url <<
"to version" << version;
600 snapshotByUrl(url).dump(qDebug() <<
"postSnapshot",
601 OpenDocumentSnapshot::DumpOption::AllCode);
604 emit updatedSnapshot(url, policy);
609 QMutexLocker l(&m_mutex);
610 m_openDocuments.remove(url);
615 QMutexLocker l(&m_mutex);
621 QMutexLocker l(&m_mutex);
627 QStringList result = importPaths();
630 if (result.isEmpty() || result == QLibraryInfo::paths(QLibraryInfo::QmlImportsPath))
631 result << buildPathsForFileUrl(url);
633 const QString importPathsKey = u"importPaths"_s;
634 const QString fileName = url2Path(url);
635 if (m_settings && m_settings->search(fileName, { QString(), verbose() }).isValid()
636 && m_settings->isSet(importPathsKey)) {
637 result.append(m_settings->valueAsAbsolutePathList(importPathsKey, fileName));
644 QMutexLocker guard(&m_mutex);
645 m_importPaths = importPaths;
647 if (
const auto &env = m_currentEnv.ownerAs<DomEnvironment>())
648 env->setLoadPaths(importPaths);
649 if (
const auto &env = m_validEnv.ownerAs<DomEnvironment>())
650 env->setLoadPaths(importPaths);
655 QMutexLocker guard(&m_mutex);
656 return m_resourceFiles;
661 QMutexLocker guard(&m_mutex);
662 m_resourceFiles = resourceFiles;
664 if (
const auto &env = m_currentEnv.ownerAs<DomEnvironment>())
665 env->setResourceFiles(resourceFiles);
666 if (
const auto &env = m_validEnv.ownerAs<DomEnvironment>())
667 env->setResourceFiles(resourceFiles);
672 QMutexLocker guard(&m_mutex);
673 return m_importPaths;
680 std::reverse(buildPaths.begin(), buildPaths.end());
681 const int maxDeps = 4;
682 while (!buildPaths.isEmpty()) {
683 res += buildPaths.takeLast();
684 const QString &bPath = res.constLast();
685 const QString bPathExtended = bPath + u"/_deps";
686 if (QFile::exists(bPathExtended) && bPath.count(u"/_deps/"_s) < maxDeps) {
687 for (
const auto &fileInfo :
688 QDirListing{ bPathExtended, QDirListing::IteratorFlag::DirsOnly }) {
689 buildPaths.append(fileInfo.absoluteFilePath());
698 if (QStringList result = buildPaths(); !result.isEmpty())
699 return withDependentBuildDirectories(std::move(result));
702 if (!m_settings || !m_settings->search(url2Path(url), { QString(), verbose() }).isValid())
705 constexpr QLatin1String buildDir =
"buildDir"_L1;
706 if (!m_settings->isSet(buildDir))
709 const QString fileName = url2Path(url);
710 return withDependentBuildDirectories(m_settings->valueAsAbsolutePathList(buildDir, fileName));
716 QMutexLocker l(&m_mutex);
717 if (m_documentationRootPath == path)
719 m_documentationRootPath = path;
721 m_helpManager.setDocumentationRootPath(path);
726 QMutexLocker l(&m_mutex);
727 m_buildPaths = paths;
732 std::optional<
int> rNow = 0;
736 QMutexLocker l(&m_mutex);
737 Q_ASSERT(QThread::currentThread() == m_openUpdateThread);
739 std::shared_ptr<Utils::TextDocument> document = doc.textDocument;
743 QMutexLocker l2(document->mutex());
744 rNow = document->version();
747 || (policy != ForceUpdate && doc.snapshot.docVersion
748 && *doc.snapshot.docVersion == *rNow)) {
753 QMutexLocker l2(doc.textDocument->mutex());
754 rNow = doc.textDocument->version();
755 docText = doc.textDocument->toPlainText();
758 newDocForOpenFile(url, *rNow, docText, policy);
764 QMutexLocker l(&m_mutex);
765 m_openDocumentsToUpdate[url] = policy;
772 dbg.noquote().nospace() <<
"{";
773 dbg <<
" url:" << QString::fromUtf8(url) <<
"\n";
774 dbg <<
" docVersion:" << (docVersion ? QString::number(*docVersion) : u"*none*"_s) <<
"\n";
776 dbg <<
" doc: ------------\n"
777 << doc.field(Fields::code).value().toString() <<
"\n==========\n";
780 << (doc ? u"%1chars"_s.arg(doc.field(Fields::code).value().toString().size())
784 dbg <<
" validDocVersion:"
785 << (validDocVersion ? QString::number(*validDocVersion) : u"*none*"_s) <<
"\n";
787 dbg <<
" validDoc: ------------\n"
788 << validDoc.field(Fields::code).value().toString() <<
"\n==========\n";
791 << (validDoc ? u"%1chars"_s.arg(validDoc.field(Fields::code).value().toString().size())
795 dbg <<
" scopeDependenciesLoadTime:" << scopeDependenciesLoadTime <<
"\n";
805 return &moduleSettings->emplaceBack(
ModuleSetting {sourceFolder, {}, {}});
807 auto it = std::find_if(
808 moduleSettings->begin(), moduleSettings->end(),
809 [&sourceFolder](
const ModuleSetting s) {
return s.sourceFolder == sourceFolder; });
810 if (it == moduleSettings->end())
811 return &moduleSettings->emplaceBack(
ModuleSetting {sourceFolder, {}, {}});
817#if QT_CONFIG(settings)
818 for (
const QString &path : buildPaths) {
819 if (policy != ForceUpdate && m_seenSettings.contains(path))
821 m_seenSettings.insert(path);
823 const QString iniPath = QString(path).append(
"/.qt/.qmlls.build.ini"_L1);
824 if (!QFile::exists(iniPath))
827 QSettings settings(iniPath, QSettings::IniFormat);
828 m_docDir = settings.value(
"docDir"_L1).toString();
829 const qsizetype version = settings.value(
"version"_L1,
"1"_L1).toString().toInt();
832 const int entries = settings.beginReadArray(
"workspaces"_L1);
833 for (
int i = 0; i < entries; ++i) {
834 settings.setArrayIndex(i);
836 ModuleSetting *moduleSetting = moduleSettingFor(
837 settings.value(
"sourcePath").toString(), &m_moduleSettings, policy);
838 moduleSetting->importPaths =
839 settings.value(
"importPaths"_L1)
841 .split(QDir::listSeparator(), Qt::SkipEmptyParts);
842 moduleSetting->resourceFiles =
843 settings.value(
"resourceFiles"_L1)
845 .split(QDir::listSeparator(), Qt::SkipEmptyParts);
852 for (
const QString &group : settings.childGroups()) {
853 settings.beginGroup(group);
855 ModuleSetting *moduleSetting = moduleSettingFor(
856 QString(group).replace(
"<SLASH>"_L1,
"/"_L1), &m_moduleSettings, policy);
857 moduleSetting->importPaths =
858 settings.value(
"importPaths"_L1)
860 .split(QDir::listSeparator(), Qt::SkipEmptyParts);
861 moduleSetting->resourceFiles =
862 settings.value(
"resourceFiles"_L1)
864 .split(QDir::listSeparator(), Qt::SkipEmptyParts);
872 Q_UNUSED(buildPaths);
878 return settingFor(filePath).importPaths;
883 return settingFor(filePath).resourceFiles;
889 qsizetype longestMatch = 0;
890 for (
const ModuleSetting &setting : m_moduleSettings) {
891 const qsizetype matchLength = setting.sourceFolder.size();
892 if (filePath.startsWith(setting.sourceFolder) && matchLength > longestMatch) {
894 longestMatch = matchLength;
897 QQmlToolingSettings::resolveRelativeImportPaths(filePath, &result.importPaths);
903 m_moduleSettings.append(moduleSetting);
908#if QT_CONFIG(settings)
909 QSettings settings(file, QSettings::IniFormat);
910 settings.setValue(
"docDir"_L1, m_docDir);
911 settings.setValue(
"version"_L1,
"2"_L1);
912 settings.beginWriteArray(
"workspaces"_L1, m_moduleSettings.size());
913 for (
int i = 0; i < m_moduleSettings.size(); ++i) {
914 settings.setArrayIndex(i);
915 settings.setValue(
"sourcePath"_L1, m_moduleSettings[i].sourceFolder);
916 settings.setValue(
"importPaths"_L1,
917 m_moduleSettings[i].importPaths.join(QDir::listSeparator()));
918 settings.setValue(
"resourceFiles"_L1,
919 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 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)