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); });
128 setCMakeStatus(HasCMake);
134
135
136
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);
148 QObject::disconnect(&m_cppFileWatcher, &QFileSystemWatcher::fileChanged,
nullptr,
nullptr);
152 QMutexLocker l(&m_mutex);
153 m_openDocumentsToUpdate.clear();
154 shouldWait = m_nUpdateInProgress != 0;
158 QThread::yieldCurrentThread();
164 return openDocumentByUrl(url).snapshot;
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);
176QString
QQmlCodeModel::url2Path(
const QByteArray &url, UrlLookup options)
180 QMutexLocker l(&m_mutex);
181 res = m_url2path.value(url);
183 if (!res.isEmpty() && options == UrlLookup::Caching)
185 QUrl qurl(QString::fromUtf8(url));
186 QFileInfo f(qurl.toLocalFile());
187 QString cPath = f.canonicalFilePath();
189 cPath = f.filePath();
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);
203 QMutexLocker l(&m_mutex);
204 auto &openDoc = m_openDocuments[url];
205 if (!openDoc.textDocument)
207 QMutexLocker l2(openDoc.textDocument->mutex());
208 openDoc.textDocument->setVersion(version);
209 openDoc.textDocument->setPlainText(docText);
211 addOpenToUpdate(url, NormalUpdate);
217 QMutexLocker l(&m_mutex);
218 return m_openDocuments.value(url);
223 QMutexLocker l(&m_mutex);
224 return m_openDocuments.isEmpty();
229 QMutexLocker l(&m_mutex);
235 QMutexLocker l(&m_mutex);
241 qCDebug(codeModelLog) <<
"openNeedUpdate";
242 const int maxThreads = 1;
244 QMutexLocker l(&m_mutex);
245 if (m_openDocumentsToUpdate.isEmpty() || m_nUpdateInProgress >= maxThreads)
247 if (++m_nUpdateInProgress == 1)
250 QThreadPool::globalInstance()->start([
this]() {
251 while (openUpdateSome()) { }
257 qCDebug(codeModelLog) <<
"openUpdateSome start";
261 QMutexLocker l(&m_mutex);
262 if (m_openDocumentsToUpdate.isEmpty()) {
263 if (--m_nUpdateInProgress == 0)
267 const auto it = m_openDocumentsToUpdate.begin();
270 m_openDocumentsToUpdate.erase(it);
272 bool hasMore =
false;
274 auto guard = qScopeGuard([
this, &hasMore]() {
275 QMutexLocker l(&m_mutex);
276 if (m_openDocumentsToUpdate.isEmpty()) {
277 if (--m_nUpdateInProgress == 0)
284 openUpdate(toUpdate, policy);
291 qCDebug(codeModelLog) <<
"openUpdateStart";
296 qCDebug(codeModelLog) <<
"openUpdateEnd";
303 std::vector<QByteArray> urls = { m_rootUrl };
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());
310 for (
const auto &url : urls)
311 result.append(buildPathsForFileUrl(url));
314 std::sort(result.begin(), result.end());
315 result.erase(std::unique(result.begin(), result.end()), result.end());
321 const QStringList buildPaths = buildPathsForOpenedFiles();
323 QList<QProcessScheduler::Command> commands;
324 for (
const auto &path : buildPaths) {
325 if (!QFileInfo::exists(path + u"/.cmake"_s))
328 const auto [program, arguments] = QQmlLSUtils::cmakeBuildCommand(path);
329 commands.append({ std::move(program), std::move(arguments) });
331 if (commands.isEmpty())
333 scheduler->schedule(commands, m_rootUrl);
336void QQmlCodeModel::onCMakeProcessFinished(
const QByteArray &id)
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;
349
350
351
352
353
354
356 const QSet<QString> &ignoredFilePaths)
358 Q_ASSERT(!m_rootUrl.isEmpty());
359 QStringList fileNamesToSearch{ _fileNamesToSearch };
362 QMutexLocker guard(&m_mutex);
364 fileNamesToSearch.erase(std::remove_if(fileNamesToSearch.begin(), fileNamesToSearch.end(),
365 [
this](
const QString &fileName) {
366 return m_ignoreForWatching.contains(fileName);
368 fileNamesToSearch.end());
371 const QString rootDir = QUrl(QString::fromUtf8(m_rootUrl)).toLocalFile();
372 qCDebug(codeModelLog) <<
"Searching for files to watch in workspace folder" << rootDir;
374 const QStringList result =
375 QQmlLSUtils::findFilePathsFromFileNames(rootDir, fileNamesToSearch, ignoredFilePaths);
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);
388
389
390
391
394 const QmlFile *file = qmlFile.as<QmlFile>();
398 auto resolver = file->typeResolver();
402 auto types = resolver->importedTypes();
405 for (
const auto &type : types) {
409 const bool isComposite = type.scope.factory() || type.scope->isComposite();
413 const QString filePath = QFileInfo(type.scope->filePath()).fileName();
414 if (!filePath.isEmpty())
418 std::sort(result.begin(), result.end());
419 result.erase(std::unique(result.begin(), result.end()), result.end());
425
426
427
428
431 if (m_rootUrl.isEmpty())
433 const auto filesToWatch = fileNamesToWatch(qmlFile);
436 const QStringList alreadyWatchedFiles = m_cppFileWatcher.files();
437 const QSet<QString> alreadyWatchedFilesSet(alreadyWatchedFiles.begin(),
438 alreadyWatchedFiles.end());
439 QStringList filepathsToWatch = findFilePathsFromFileNames(filesToWatch, alreadyWatchedFilesSet);
441 if (filepathsToWatch.isEmpty())
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"
466 if (!doc.textDocument)
470 QMutexLocker guard2(doc.textDocument->mutex());
471 if (doc.textDocument->version() && *doc.textDocument->version() > version)
475 if (doc.snapshot.docVersion && *doc.snapshot.docVersion >= version)
484 if (doc.snapshot.validDocVersion && *doc.snapshot.validDocVersion >= version)
496 qCWarning(lspServerLog) <<
"Ignoring update to closed document" << QString::fromUtf8(url);
498 case VersionLowerThanDocument:
499 qCWarning(lspServerLog) <<
"Version" << version <<
"of document" << QString::fromUtf8(url)
500 <<
"is not the latest anymore";
502 case VersionLowerThanSnapshot:
503 qCWarning(lspServerLog) <<
"Skipping update of current doc to obsolete version" << version
504 <<
"of document" << QString::fromUtf8(url);
507 doc->snapshot.docVersion = version;
508 doc->snapshot.doc = item;
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";
520 case VersionLowerThanValidSnapshot:
521 qCWarning(lspServerLog) <<
"Skipping update of valid doc to obsolete version" << version
522 <<
"of document" << QString::fromUtf8(url);
525 doc->snapshot.validDocVersion = version;
526 doc->snapshot.validDoc = validItem;
534 qCDebug(codeModelLog) <<
"updating doc" << url <<
"to version" << version <<
"("
535 << docText.size() <<
"chars)";
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);
541 if (std::shared_ptr<DomEnvironment> newCurrentPtr = newCurrent.ownerAs<DomEnvironment>()) {
542 newCurrentPtr->setLoadPaths(loadPaths);
547 if (documentationRootPath().isEmpty() && m_settings) {
549 const QString docDir = QStringLiteral(u"docDir");
550 if (m_settings->isSet(docDir))
551 setDocumentationRootPath(m_settings->value(docDir).toString());
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();
563 file.field(Fields::isValid);
564 if (cmakeStatus() == HasCMake)
565 addFileWatches(file);
567 newCurrentPtr->loadPendingDependencies();
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],
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);
580 emit updatedSnapshot(url, policy);
585 QMutexLocker l(&m_mutex);
586 m_openDocuments.remove(url);
591 QMutexLocker l(&m_mutex);
597 QMutexLocker l(&m_mutex);
603 const QString fileName = url2Path(url);
604 QStringList result = importPaths();
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()));
612 const QStringList buildPath = buildPathsForFileUrl(url);
613 QMutexLocker l(&m_mutex);
614 m_buildInformation.loadSettingsFrom(buildPath);
615 result.append(m_buildInformation.importPathsFor(fileName));
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);
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();
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());
659 if (QStringList result = buildPaths(); !result.isEmpty())
660 return withDependentBuildDirectories(std::move(result));
663 if (!m_settings || !m_settings->search(url2Path(url), { QString(), verbose() }).isValid())
666 constexpr QLatin1String buildDir =
"buildDir"_L1;
667 if (!m_settings->isSet(buildDir))
670 return withDependentBuildDirectories(m_settings->value(buildDir).toString().split(
671 QDir::listSeparator(), Qt::SkipEmptyParts));
677 QMutexLocker l(&m_mutex);
678 if (m_documentationRootPath == path)
680 m_documentationRootPath = path;
682 m_helpManager.setDocumentationRootPath(path);
687 QMutexLocker l(&m_mutex);
688 m_buildPaths = paths;
693 std::optional<
int> rNow = 0;
697 QMutexLocker l(&m_mutex);
703 QMutexLocker l2(document->mutex());
704 rNow = document->version();
707 || (policy !=
ForceUpdate && doc.snapshot.docVersion
708 && *doc.snapshot.docVersion == *rNow)) {
713 QMutexLocker l2(doc.textDocument->mutex());
714 rNow = doc.textDocument->version();
715 docText = doc.textDocument->toPlainText();
718 newDocForOpenFile(url, *rNow, docText, policy);
724 QMutexLocker l(&m_mutex);
725 m_openDocumentsToUpdate[url] = policy;
732 dbg.noquote().nospace() <<
"{";
733 dbg <<
" url:" << QString::fromUtf8(url) <<
"\n";
734 dbg <<
" docVersion:" << (docVersion ? QString::number(*docVersion) : u"*none*"_s) <<
"\n";
736 dbg <<
" doc: ------------\n"
737 << doc.field(Fields::code).value().toString() <<
"\n==========\n";
740 << (doc ? u"%1chars"_s.arg(doc.field(Fields::code).value().toString().size())
744 dbg <<
" validDocVersion:"
745 << (validDocVersion ? QString::number(*validDocVersion) : u"*none*"_s) <<
"\n";
747 dbg <<
" validDoc: ------------\n"
748 << validDoc.field(Fields::code).value().toString() <<
"\n==========\n";
751 << (validDoc ? u"%1chars"_s.arg(validDoc.field(Fields::code).value().toString().size())
755 dbg <<
" scopeDependenciesLoadTime:" << scopeDependenciesLoadTime <<
"\n";
763#if QT_CONFIG(settings)
764 for (
const QString &path : buildPaths) {
765 if (m_seenSettings.contains(path))
767 m_seenSettings.insert(path);
769 const QString iniPath = QString(path).append(
"/.qt/.qmlls.build.ini"_L1);
770 if (!QFile::exists(iniPath))
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);
778 ModuleSetting moduleSetting;
779 moduleSetting.sourceFolder = group;
780 moduleSetting.sourceFolder.replace(
"<SLASH>"_L1,
"/"_L1);
781 moduleSetting.importPaths = settings.value(
"importPaths"_L1)
783 .split(QDir::listSeparator(), Qt::SkipEmptyParts);
784 m_moduleSettings.append(moduleSetting);
789 Q_UNUSED(buildPaths);
795 return settingFor(filePath).importPaths;
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) {
806 longestMatch = matchLength;
bool scopeDependenciesChanged
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)
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)