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);
146 QObject::disconnect(&m_cppFileWatcher, &QFileSystemWatcher::fileChanged,
nullptr,
nullptr);
150 QMutexLocker l(&m_mutex);
151 m_openDocumentsToUpdate.clear();
152 shouldWait = m_nUpdateInProgress != 0;
156 QThread::yieldCurrentThread();
162 return openDocumentByUrl(url).snapshot;
167 const QString path = url2Path(url);
168 if (
auto validEnvPtr = m_validEnv.ownerAs<DomEnvironment>())
169 validEnvPtr->removePath(path);
170 if (
auto currentEnvPtr = m_currentEnv.ownerAs<DomEnvironment>())
171 currentEnvPtr->removePath(path);
174QString
QQmlCodeModel::url2Path(
const QByteArray &url, UrlLookup options)
178 QMutexLocker l(&m_mutex);
179 res = m_url2path.value(url);
181 if (!res.isEmpty() && options == UrlLookup::Caching)
183 QUrl qurl(QString::fromUtf8(url));
184 QFileInfo f(qurl.toLocalFile());
185 QString cPath = f.canonicalFilePath();
187 cPath = f.filePath();
189 QMutexLocker l(&m_mutex);
190 if (!res.isEmpty() && res != cPath)
191 m_path2url.remove(res);
192 m_url2path.insert(url, cPath);
193 m_path2url.insert(cPath, url);
201 QMutexLocker l(&m_mutex);
202 auto &openDoc = m_openDocuments[url];
203 if (!openDoc.textDocument)
205 QMutexLocker l2(openDoc.textDocument->mutex());
206 openDoc.textDocument->setVersion(version);
207 openDoc.textDocument->setPlainText(docText);
209 addOpenToUpdate(url, NormalUpdate);
215 QMutexLocker l(&m_mutex);
216 return m_openDocuments.value(url);
221 QMutexLocker l(&m_mutex);
222 return m_openDocuments.isEmpty();
227 QMutexLocker l(&m_mutex);
233 QMutexLocker l(&m_mutex);
239 qCDebug(codeModelLog) <<
"openNeedUpdate";
240 const int maxThreads = 1;
242 QMutexLocker l(&m_mutex);
243 if (m_openDocumentsToUpdate.isEmpty() || m_nUpdateInProgress >= maxThreads)
245 if (++m_nUpdateInProgress == 1)
248 QThreadPool::globalInstance()->start([
this]() {
249 while (openUpdateSome()) { }
255 qCDebug(codeModelLog) <<
"openUpdateSome start";
259 QMutexLocker l(&m_mutex);
260 if (m_openDocumentsToUpdate.isEmpty()) {
261 if (--m_nUpdateInProgress == 0)
265 const auto it = m_openDocumentsToUpdate.begin();
268 m_openDocumentsToUpdate.erase(it);
270 bool hasMore =
false;
272 auto guard = qScopeGuard([
this, &hasMore]() {
273 QMutexLocker l(&m_mutex);
274 if (m_openDocumentsToUpdate.isEmpty()) {
275 if (--m_nUpdateInProgress == 0)
282 openUpdate(toUpdate, policy);
289 qCDebug(codeModelLog) <<
"openUpdateStart";
294 qCDebug(codeModelLog) <<
"openUpdateEnd";
301 std::vector<QByteArray> urls = { m_rootUrl };
303 QMutexLocker guard(&m_mutex);
304 for (
auto it = m_openDocuments.begin(), end = m_openDocuments.end(); it != end; ++it)
305 urls.push_back(it.key());
308 for (
const auto &url : urls)
309 result.append(buildPathsForFileUrl(url));
312 std::sort(result.begin(), result.end());
313 result.erase(std::unique(result.begin(), result.end()), result.end());
319 const QStringList buildPaths = buildPathsForOpenedFiles();
321 QList<QProcessScheduler::Command> commands;
322 for (
const auto &path : buildPaths) {
323 if (!QFileInfo::exists(path + u"/.cmake"_s))
326 const auto [program, arguments] = QQmlLSUtils::cmakeBuildCommand(path);
327 commands.append({ std::move(program), std::move(arguments) });
329 if (commands.isEmpty())
331 scheduler->schedule(commands, m_rootUrl);
334void QQmlCodeModel::onCMakeProcessFinished(
const QByteArray &id)
339 QMutexLocker guard(&m_mutex);
340 for (
auto it = m_openDocuments.begin(), end = m_openDocuments.end(); it != end; ++it)
341 m_openDocumentsToUpdate[it.key()] = ForceUpdate;
347
348
349
350
351
352
354 const QSet<QString> &ignoredFilePaths)
356 Q_ASSERT(!m_rootUrl.isEmpty());
357 QStringList fileNamesToSearch{ _fileNamesToSearch };
360 QMutexLocker guard(&m_mutex);
362 fileNamesToSearch.erase(std::remove_if(fileNamesToSearch.begin(), fileNamesToSearch.end(),
363 [
this](
const QString &fileName) {
364 return m_ignoreForWatching.contains(fileName);
366 fileNamesToSearch.end());
369 const QString rootDir = QUrl(QString::fromUtf8(m_rootUrl)).toLocalFile();
370 qCDebug(codeModelLog) <<
"Searching for files to watch in workspace folder" << rootDir;
372 const QStringList result =
373 QQmlLSUtils::findFilePathsFromFileNames(rootDir, fileNamesToSearch, ignoredFilePaths);
375 QMutexLocker guard(&m_mutex);
376 for (
const auto &fileName : fileNamesToSearch) {
377 if (std::none_of(result.begin(), result.end(),
378 [&fileName](
const QString &path) {
return path.endsWith(fileName); })) {
379 m_ignoreForWatching.insert(fileName);
386
387
388
389
392 const QmlFile *file = qmlFile.as<QmlFile>();
396 auto resolver = file->typeResolver();
400 auto types = resolver->importedTypes();
403 for (
const auto &type : types) {
407 const bool isComposite = type.scope.factory() || type.scope->isComposite();
411 const QString filePath = QFileInfo(type.scope->filePath()).fileName();
412 if (!filePath.isEmpty())
416 std::sort(result.begin(), result.end());
417 result.erase(std::unique(result.begin(), result.end()), result.end());
423
424
425
426
429 if (m_rootUrl.isEmpty())
431 const auto filesToWatch = fileNamesToWatch(qmlFile);
434 const QStringList alreadyWatchedFiles = m_cppFileWatcher.files();
435 const QSet<QString> alreadyWatchedFilesSet(alreadyWatchedFiles.begin(),
436 alreadyWatchedFiles.end());
437 QStringList filepathsToWatch = findFilePathsFromFileNames(filesToWatch, alreadyWatchedFilesSet);
439 if (filepathsToWatch.isEmpty())
442 QMutexLocker guard(&m_mutex);
443 const auto unwatchedPaths = m_cppFileWatcher.addPaths(filepathsToWatch);
444 if (!unwatchedPaths.isEmpty()) {
445 qCDebug(codeModelLog) <<
"Cannot watch paths" << unwatchedPaths <<
"from requested"
464 if (!doc.textDocument)
468 QMutexLocker guard2(doc.textDocument->mutex());
469 if (doc.textDocument->version() && *doc.textDocument->version() > version)
473 if (doc.snapshot.docVersion && *doc.snapshot.docVersion >= version)
482 if (doc.snapshot.validDocVersion && *doc.snapshot.validDocVersion >= version)
494 qCWarning(lspServerLog) <<
"Ignoring update to closed document" << QString::fromUtf8(url);
496 case VersionLowerThanDocument:
497 qCWarning(lspServerLog) <<
"Version" << version <<
"of document" << QString::fromUtf8(url)
498 <<
"is not the latest anymore";
500 case VersionLowerThanSnapshot:
501 qCWarning(lspServerLog) <<
"Skipping update of current doc to obsolete version" << version
502 <<
"of document" << QString::fromUtf8(url);
505 doc->snapshot.docVersion = version;
506 doc->snapshot.doc = item;
510 if (!item.field(Fields::isValid).value().toBool(
false)) {
511 qCWarning(lspServerLog) <<
"avoid update of validDoc to " << version <<
"of document"
512 << QString::fromUtf8(url) <<
"as it is invalid";
518 case VersionLowerThanValidSnapshot:
519 qCWarning(lspServerLog) <<
"Skipping update of valid doc to obsolete version" << version
520 <<
"of document" << QString::fromUtf8(url);
523 doc->snapshot.validDocVersion = version;
524 doc->snapshot.validDoc = validItem;
532 qCDebug(codeModelLog) <<
"updating doc" << url <<
"to version" << version <<
"("
533 << docText.size() <<
"chars)";
535 const QString fPath = url2Path(url, UrlLookup::ForceLookup);
536 DomItem newCurrent = m_currentEnv.makeCopy(DomItem::CopyOption::EnvConnected).item();
540 if (documentationRootPath().isEmpty() && m_settings) {
542 const QString docDir = QStringLiteral(u"docDir");
543 if (m_settings->isSet(docDir))
544 setDocumentationRootPath(m_settings->value(docDir).toString());
548 auto newCurrentPtr = newCurrent.ownerAs<DomEnvironment>();
549 const QStringList loadPaths = buildPathsForFileUrl(url) + importPathsForUrl(url);
550 newCurrentPtr->setLoadPaths(loadPaths);
551 newCurrentPtr->loadFile(FileToLoad::fromMemory(newCurrentPtr, fPath, docText),
552 [&p,
this](Path,
const DomItem &,
const DomItem &newValue) {
553 const DomItem file = newValue.fileObject();
554 p = file.canonicalPath();
558 file.field(Fields::isValid);
559 if (cmakeStatus() == HasCMake)
560 addFileWatches(file);
562 newCurrentPtr->loadPendingDependencies();
564 newCurrent.commitToBase(m_validEnv.ownerAs<DomEnvironment>());
565 QMutexLocker l(&m_mutex);
566 updateItemInSnapshot(m_currentEnv.path(p), m_validEnv.path(p), url, &m_openDocuments[url],
569 if (codeModelLog().isDebugEnabled()) {
570 qCDebug(codeModelLog) <<
"Finished update doc of " << url <<
"to version" << version;
571 snapshotByUrl(url).dump(qDebug() <<
"postSnapshot",
572 OpenDocumentSnapshot::DumpOption::AllCode);
575 emit updatedSnapshot(url, policy);
580 QMutexLocker l(&m_mutex);
581 m_openDocuments.remove(url);
586 QMutexLocker l(&m_mutex);
592 QMutexLocker l(&m_mutex);
598 QStringList result = importPaths();
600 const QString importPathsKey = u"importPaths"_s;
601 const QString fileName = url2Path(url);
602 if (m_settings && m_settings->search(fileName, { QString(), verbose() }).isValid()
603 && m_settings->isSet(importPathsKey)) {
604 result.append(m_settings->valueAsAbsolutePathList(importPathsKey, fileName));
607 const QStringList buildPath = buildPathsForFileUrl(url);
608 QMutexLocker l(&m_mutex);
609 m_buildInformation.loadSettingsFrom(buildPath);
610 result.append(m_buildInformation.importPathsFor(fileName));
617 const QStringList loadPaths = m_importPaths + m_buildPaths;
618 if (
const auto &env = m_currentEnv.ownerAs<DomEnvironment>())
619 env->setLoadPaths(loadPaths);
620 if (
const auto &env = m_validEnv.ownerAs<DomEnvironment>())
621 env->setLoadPaths(loadPaths);
626 QMutexLocker guard(&m_mutex);
627 m_importPaths = importPaths;
628 onBuildOrImportPathChanged();
633 QMutexLocker guard(&m_mutex);
634 return m_importPaths;
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;
689 onBuildOrImportPathChanged();
694 std::optional<
int> rNow = 0;
698 QMutexLocker l(&m_mutex);
704 QMutexLocker l2(document->mutex());
705 rNow = document->version();
708 || (policy !=
ForceUpdate && doc.snapshot.docVersion
709 && *doc.snapshot.docVersion == *rNow)) {
714 QMutexLocker l2(doc.textDocument->mutex());
715 rNow = doc.textDocument->version();
716 docText = doc.textDocument->toPlainText();
719 newDocForOpenFile(url, *rNow, docText, policy);
725 QMutexLocker l(&m_mutex);
726 m_openDocumentsToUpdate[url] = policy;
733 dbg.noquote().nospace() <<
"{";
734 dbg <<
" url:" << QString::fromUtf8(url) <<
"\n";
735 dbg <<
" docVersion:" << (docVersion ? QString::number(*docVersion) : u"*none*"_s) <<
"\n";
737 dbg <<
" doc: ------------\n"
738 << doc.field(Fields::code).value().toString() <<
"\n==========\n";
741 << (doc ? u"%1chars"_s.arg(doc.field(Fields::code).value().toString().size())
745 dbg <<
" validDocVersion:"
746 << (validDocVersion ? QString::number(*validDocVersion) : u"*none*"_s) <<
"\n";
748 dbg <<
" validDoc: ------------\n"
749 << validDoc.field(Fields::code).value().toString() <<
"\n==========\n";
752 << (validDoc ? u"%1chars"_s.arg(validDoc.field(Fields::code).value().toString().size())
756 dbg <<
" scopeDependenciesLoadTime:" << scopeDependenciesLoadTime <<
"\n";
764#if QT_CONFIG(settings)
765 for (
const QString &path : buildPaths) {
766 if (m_seenSettings.contains(path))
768 m_seenSettings.insert(path);
770 const QString iniPath = QString(path).append(
"/.qt/.qmlls.build.ini"_L1);
771 if (!QFile::exists(iniPath))
774 QSettings settings(iniPath, QSettings::IniFormat);
775 m_docDir = settings.value(
"docDir"_L1).toString();
776 for (
const QString &group : settings.childGroups()) {
777 settings.beginGroup(group);
779 ModuleSetting moduleSetting;
780 moduleSetting.sourceFolder = group;
781 moduleSetting.sourceFolder.replace(
"<SLASH>"_L1,
"/"_L1);
782 moduleSetting.importPaths = settings.value(
"importPaths"_L1)
784 .split(QDir::listSeparator(), Qt::SkipEmptyParts);
785 m_moduleSettings.append(moduleSetting);
790 Q_UNUSED(buildPaths);
796 return settingFor(filePath).importPaths;
802 qsizetype longestMatch = 0;
803 for (
const ModuleSetting &setting : m_moduleSettings) {
804 const qsizetype matchLength = setting.sourceFolder.size();
805 if (filePath.startsWith(setting.sourceFolder) && matchLength > longestMatch) {
807 longestMatch = matchLength;
810 QQmlToolingSettings::resolveRelativeImportPaths(filePath, &result.importPaths);
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)
Combined button and popup list for selecting options.
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)