1// Copyright (C) 2021 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
3
4#include "qqmlcodemodel_p.h"
5#include "qtextdocument_p.h"
6
7#include <QtCore/qfileinfo.h>
8#include <QtCore/qdir.h>
9#include <QtCore/qthreadpool.h>
10#include <QtCore/qlibraryinfo.h>
11#include <QtQmlDom/private/qqmldomtop_p.h>
12
13#include <memory>
14#include <algorithm>
15
16QT_BEGIN_NAMESPACE
17
18namespace QmlLsp {
19
20Q_LOGGING_CATEGORY(codeModelLog, "qt.languageserver.codemodel")
21
22using namespace QQmlJS::Dom;
23using namespace Qt::StringLiterals;
24
25/*!
26\internal
27\class QQmlCodeModel
28
29The code model offers a view of the current state of the current files, and traks open files.
30All methods are threadsafe, and generally return immutable or threadsafe objects that can be
31worked on from any thread (unless otherwise noted).
32The idea is the let all other operations be as lock free as possible, concentrating all tricky
33synchronization here.
34
35\section2 Global views
36\list
37\li currentEnv() offers a view that contains the latest version of all the loaded files
38\li validEnv() is just like current env but stores only the valid (meaning correctly parsed,
39 not necessarily without errors) version of a file, it is normally a better choice to load the
40 dependencies/symbol information from
41\endlist
42
43\section2 OpenFiles
44\list
45\li snapshotByUrl() returns an OpenDocumentSnapshot of an open document. From it you can get the
46 document, its latest valid version, scope, all connected to a specific version of the document
47 and immutable. The signal updatedSnapshot() is called every time a snapshot changes (also for
48 every partial change: document change, validDocument change, scope change).
49\li openDocumentByUrl() is a lower level and more intrusive access to OpenDocument objects. These
50 contains the current snapshot, and shared pointer to a Utils::TextDocument. This is *always* the
51 current version of the document, and has line by line support.
52 Working on it is more delicate and intrusive, because you have to explicitly acquire its mutex()
53 before *any* read or write/modification to it.
54 It has a version nuber which is supposed to always change and increase.
55 It is mainly used for highlighting/indenting, and is immediately updated when the user edits a
56 document. Its use should be avoided if possible, preferring the snapshots.
57\endlist
58
59\section2 Parallelism/Theading
60Most operations are not parallel and usually take place in the main thread (but are still thread
61safe).
62There are two main task that are executed in parallel: Indexing, and OpenDocumentUpdate.
63Indexing is meant to keep the global view up to date.
64OpenDocumentUpdate keeps the snapshots of the open documents up to date.
65
66There is always a tension between being responsive, using all threads available, and avoid to hog
67too many resources. One can choose different parallelization strategies, we went with a flexiable
68approach.
69We have (private) functions that execute part of the work: indexSome() and openUpdateSome(). These
70do all locking needed, get some work, do it without locks, and at the end update the state of the
71code model. If there is more work, then they return true. Thus while (xxxSome()); works until there
72is no work left.
73
74addDirectoriesToIndex(), the internal addDirectory() and addOpenToUpdate() add more work to do.
75
76indexNeedsUpdate() and openNeedUpdate(), check if there is work to do, and if yes ensure that a
77worker thread (or more) that work on it exist.
78*/
79
80QQmlCodeModel::QQmlCodeModel(QObject *parent, QQmlToolingSettings *settings)
81 : QObject { parent },
82 m_currentEnv(std::make_shared<DomEnvironment>(
83 args: QStringList(QLibraryInfo::path(p: QLibraryInfo::QmlImportsPath)),
84 args: DomEnvironment::Option::SingleThreaded)),
85 m_validEnv(std::make_shared<DomEnvironment>(
86 args: QStringList(QLibraryInfo::path(p: QLibraryInfo::QmlImportsPath)),
87 args: DomEnvironment::Option::SingleThreaded)),
88 m_settings(settings)
89{
90}
91
92QQmlCodeModel::~QQmlCodeModel()
93{
94 while (true) {
95 bool shouldWait;
96 {
97 QMutexLocker l(&m_mutex);
98 m_state = State::Stopping;
99 m_openDocumentsToUpdate.clear();
100 shouldWait = m_nIndexInProgress != 0 || m_nUpdateInProgress != 0;
101 }
102 if (!shouldWait)
103 break;
104 QThread::yieldCurrentThread();
105 }
106}
107
108OpenDocumentSnapshot QQmlCodeModel::snapshotByUrl(const QByteArray &url)
109{
110 return openDocumentByUrl(url).snapshot;
111}
112
113int QQmlCodeModel::indexEvalProgress() const
114{
115 Q_ASSERT(!m_mutex.tryLock()); // should be called while locked
116 const int dirCost = 10;
117 int costToDo = 1;
118 for (const ToIndex &el : std::as_const(t: m_toIndex))
119 costToDo += dirCost * el.leftDepth;
120 costToDo += m_indexInProgressCost;
121 return m_indexDoneCost * 100 / (costToDo + m_indexDoneCost);
122}
123
124void QQmlCodeModel::indexStart()
125{
126 Q_ASSERT(!m_mutex.tryLock()); // should be called while locked
127 qCDebug(codeModelLog) << "indexStart";
128}
129
130void QQmlCodeModel::indexEnd()
131{
132 Q_ASSERT(!m_mutex.tryLock()); // should be called while locked
133 qCDebug(codeModelLog) << "indexEnd";
134 m_lastIndexProgress = 0;
135 m_nIndexInProgress = 0;
136 m_toIndex.clear();
137 m_indexInProgressCost = 0;
138 m_indexDoneCost = 0;
139}
140
141void QQmlCodeModel::indexSendProgress(int progress)
142{
143 if (progress <= m_lastIndexProgress)
144 return;
145 m_lastIndexProgress = progress;
146 // ### actually send progress
147}
148
149bool QQmlCodeModel::indexCancelled()
150{
151 QMutexLocker l(&m_mutex);
152 if (m_state == State::Stopping)
153 return true;
154 return false;
155}
156
157void QQmlCodeModel::indexDirectory(const QString &path, int depthLeft)
158{
159 if (indexCancelled())
160 return;
161 QDir dir(path);
162 if (depthLeft > 1) {
163 const QStringList dirs =
164 dir.entryList(filters: QDir::Dirs | QDir::NoDotAndDotDot | QDir::NoSymLinks);
165 for (const QString &child : dirs)
166 addDirectory(path: dir.filePath(fileName: child), leftDepth: --depthLeft);
167 }
168 const QStringList qmljs =
169 dir.entryList(nameFilters: QStringList({ u"*.qml"_s, u"*.js"_s, u"*.mjs"_s }), filters: QDir::Files);
170 int progress = 0;
171 {
172 QMutexLocker l(&m_mutex);
173 m_indexInProgressCost += qmljs.size();
174 progress = indexEvalProgress();
175 }
176 indexSendProgress(progress);
177 if (qmljs.isEmpty())
178 return;
179 DomItem newCurrent = m_currentEnv.makeCopy(option: DomItem::CopyOption::EnvConnected).item();
180 for (const QString &file : qmljs) {
181 if (indexCancelled())
182 return;
183 QString fPath = dir.filePath(fileName: file);
184 DomCreationOptions options;
185 options.setFlag(flag: DomCreationOption::WithScriptExpressions);
186 options.setFlag(flag: DomCreationOption::WithSemanticAnalysis);
187 FileToLoad fileToLoad =
188 FileToLoad::fromFileSystem(environment: newCurrent.ownerAs<DomEnvironment>(), canonicalPath: fPath, options);
189 if (!fileToLoad.canonicalPath().isEmpty()) {
190 newCurrent.loadBuiltins();
191 newCurrent.loadFile(file: fileToLoad, callback: [](Path, DomItem &, DomItem &) {}, loadOptions: {});
192 newCurrent.loadPendingDependencies();
193 newCurrent.commitToBase(validPtr: m_validEnv.ownerAs<DomEnvironment>());
194 }
195 {
196 QMutexLocker l(&m_mutex);
197 ++m_indexDoneCost;
198 --m_indexInProgressCost;
199 progress = indexEvalProgress();
200 }
201 indexSendProgress(progress);
202 }
203}
204
205void QQmlCodeModel::addDirectoriesToIndex(const QStringList &paths, QLanguageServer *server)
206{
207 Q_UNUSED(server);
208 // ### create progress, &scan in a separate instance
209 const int maxDepth = 5;
210 for (const auto &path : paths)
211 addDirectory(path, leftDepth: maxDepth);
212 indexNeedsUpdate();
213}
214
215void QQmlCodeModel::addDirectory(const QString &path, int depthLeft)
216{
217 if (depthLeft < 1)
218 return;
219 {
220 QMutexLocker l(&m_mutex);
221 for (auto it = m_toIndex.begin(); it != m_toIndex.end();) {
222 if (it->path.startsWith(s: path)) {
223 if (it->path.size() == path.size())
224 return;
225 if (it->path.at(i: path.size()) == u'/') {
226 it = m_toIndex.erase(pos: it);
227 continue;
228 }
229 } else if (path.startsWith(s: it->path) && path.at(i: it->path.size()) == u'/')
230 return;
231 ++it;
232 }
233 m_toIndex.append(t: { .path: path, .leftDepth: depthLeft });
234 }
235}
236
237void QQmlCodeModel::removeDirectory(const QString &path)
238{
239 {
240 QMutexLocker l(&m_mutex);
241 auto toRemove = [path](const QString &p) {
242 return p.startsWith(s: path) && (p.size() == path.size() || p.at(i: path.size()) == u'/');
243 };
244 auto it = m_toIndex.begin();
245 auto end = m_toIndex.end();
246 while (it != end) {
247 if (toRemove(it->path))
248 it = m_toIndex.erase(pos: it);
249 else
250 ++it;
251 }
252 }
253 if (auto validEnvPtr = m_validEnv.ownerAs<DomEnvironment>())
254 validEnvPtr->removePath(path);
255 if (auto currentEnvPtr = m_currentEnv.ownerAs<DomEnvironment>())
256 currentEnvPtr->removePath(path);
257}
258
259QString QQmlCodeModel::url2Path(const QByteArray &url, UrlLookup options)
260{
261 QString res;
262 {
263 QMutexLocker l(&m_mutex);
264 res = m_url2path.value(key: url);
265 }
266 if (!res.isEmpty() && options == UrlLookup::Caching)
267 return res;
268 QUrl qurl(QString::fromUtf8(ba: url));
269 QFileInfo f(qurl.toLocalFile());
270 QString cPath = f.canonicalFilePath();
271 if (cPath.isEmpty())
272 cPath = f.filePath();
273 {
274 QMutexLocker l(&m_mutex);
275 if (!res.isEmpty() && res != cPath)
276 m_path2url.remove(key: res);
277 m_url2path.insert(key: url, value: cPath);
278 m_path2url.insert(key: cPath, value: url);
279 }
280 return cPath;
281}
282
283void QQmlCodeModel::newOpenFile(const QByteArray &url, int version, const QString &docText)
284{
285 {
286 QMutexLocker l(&m_mutex);
287 auto &openDoc = m_openDocuments[url];
288 if (!openDoc.textDocument)
289 openDoc.textDocument = std::make_shared<Utils::TextDocument>();
290 QMutexLocker l2(openDoc.textDocument->mutex());
291 openDoc.textDocument->setVersion(version);
292 openDoc.textDocument->setPlainText(docText);
293 }
294 addOpenToUpdate(url);
295 openNeedUpdate();
296}
297
298OpenDocument QQmlCodeModel::openDocumentByUrl(const QByteArray &url)
299{
300 QMutexLocker l(&m_mutex);
301 return m_openDocuments.value(key: url);
302}
303
304void QQmlCodeModel::indexNeedsUpdate()
305{
306 const int maxIndexThreads = 1;
307 {
308 QMutexLocker l(&m_mutex);
309 if (m_toIndex.isEmpty() || m_nIndexInProgress >= maxIndexThreads)
310 return;
311 if (++m_nIndexInProgress == 1)
312 indexStart();
313 }
314 QThreadPool::globalInstance()->start(functionToRun: [this]() {
315 while (indexSome()) { }
316 });
317}
318
319bool QQmlCodeModel::indexSome()
320{
321 qCDebug(codeModelLog) << "indexSome";
322 ToIndex toIndex;
323 {
324 QMutexLocker l(&m_mutex);
325 if (m_toIndex.isEmpty()) {
326 if (--m_nIndexInProgress == 0)
327 indexEnd();
328 return false;
329 }
330 toIndex = m_toIndex.last();
331 m_toIndex.removeLast();
332 }
333 bool hasMore = false;
334 {
335 auto guard = qScopeGuard(f: [this, &hasMore]() {
336 QMutexLocker l(&m_mutex);
337 if (m_toIndex.isEmpty()) {
338 if (--m_nIndexInProgress == 0)
339 indexEnd();
340 hasMore = false;
341 } else {
342 hasMore = true;
343 }
344 });
345 indexDirectory(path: toIndex.path, depthLeft: toIndex.leftDepth);
346 }
347 return hasMore;
348}
349
350void QQmlCodeModel::openNeedUpdate()
351{
352 qCDebug(codeModelLog) << "openNeedUpdate";
353 const int maxIndexThreads = 1;
354 {
355 QMutexLocker l(&m_mutex);
356 if (m_openDocumentsToUpdate.isEmpty() || m_nUpdateInProgress >= maxIndexThreads)
357 return;
358 if (++m_nUpdateInProgress == 1)
359 openUpdateStart();
360 }
361 QThreadPool::globalInstance()->start(functionToRun: [this]() {
362 while (openUpdateSome()) { }
363 });
364}
365
366bool QQmlCodeModel::openUpdateSome()
367{
368 qCDebug(codeModelLog) << "openUpdateSome start";
369 QByteArray toUpdate;
370 {
371 QMutexLocker l(&m_mutex);
372 if (m_openDocumentsToUpdate.isEmpty()) {
373 if (--m_nUpdateInProgress == 0)
374 openUpdateEnd();
375 return false;
376 }
377 auto it = m_openDocumentsToUpdate.find(value: m_lastOpenDocumentUpdated);
378 auto end = m_openDocumentsToUpdate.end();
379 if (it == end)
380 it = m_openDocumentsToUpdate.begin();
381 else if (++it == end)
382 it = m_openDocumentsToUpdate.begin();
383 toUpdate = *it;
384 m_openDocumentsToUpdate.erase(i: it);
385 }
386 bool hasMore = false;
387 {
388 auto guard = qScopeGuard(f: [this, &hasMore]() {
389 QMutexLocker l(&m_mutex);
390 if (m_openDocumentsToUpdate.isEmpty()) {
391 if (--m_nUpdateInProgress == 0)
392 openUpdateEnd();
393 hasMore = false;
394 } else {
395 hasMore = true;
396 }
397 });
398 openUpdate(toUpdate);
399 }
400 return hasMore;
401}
402
403void QQmlCodeModel::openUpdateStart()
404{
405 qCDebug(codeModelLog) << "openUpdateStart";
406}
407
408void QQmlCodeModel::openUpdateEnd()
409{
410 qCDebug(codeModelLog) << "openUpdateEnd";
411}
412
413void QQmlCodeModel::newDocForOpenFile(const QByteArray &url, int version, const QString &docText)
414{
415 qCDebug(codeModelLog) << "updating doc" << url << "to version" << version << "("
416 << docText.size() << "chars)";
417 DomItem newCurrent = m_currentEnv.makeCopy(option: DomItem::CopyOption::EnvConnected).item();
418 QStringList loadPaths = buildPathsForFileUrl(url);
419 loadPaths.append(t: QLibraryInfo::path(p: QLibraryInfo::QmlImportsPath));
420 if (std::shared_ptr<DomEnvironment> newCurrentPtr = newCurrent.ownerAs<DomEnvironment>()) {
421 newCurrentPtr->setLoadPaths(loadPaths);
422 }
423 QString fPath = url2Path(url, options: UrlLookup::ForceLookup);
424 Path p;
425 DomCreationOptions options;
426 options.setFlag(flag: DomCreationOption::WithScriptExpressions);
427 options.setFlag(flag: DomCreationOption::WithSemanticAnalysis);
428 newCurrent.loadFile(
429 file: FileToLoad::fromMemory(environment: newCurrent.ownerAs<DomEnvironment>(), path: fPath, data: docText, options),
430 callback: [&p](Path, DomItem &, DomItem &newValue) { p = newValue.fileObject().canonicalPath(); },
431 loadOptions: {});
432 newCurrent.loadPendingDependencies();
433 if (p) {
434 newCurrent.commitToBase(validPtr: m_validEnv.ownerAs<DomEnvironment>());
435 DomItem item = m_currentEnv.path(p);
436 {
437 QMutexLocker l(&m_mutex);
438 OpenDocument &doc = m_openDocuments[url];
439 if (!doc.textDocument) {
440 qCWarning(lspServerLog)
441 << "ignoring update to closed document" << QString::fromUtf8(ba: url);
442 return;
443 } else {
444 QMutexLocker l(doc.textDocument->mutex());
445 if (doc.textDocument->version() && *doc.textDocument->version() > version) {
446 qCWarning(lspServerLog)
447 << "docUpdate: version" << version << "of document"
448 << QString::fromUtf8(ba: url) << "is not the latest anymore";
449 return;
450 }
451 }
452 if (!doc.snapshot.docVersion || *doc.snapshot.docVersion < version) {
453 doc.snapshot.docVersion = version;
454 doc.snapshot.doc = item;
455 } else {
456 qCWarning(lspServerLog) << "skipping update of current doc to obsolete version"
457 << version << "of document" << QString::fromUtf8(ba: url);
458 }
459 if (item.field(name: Fields::isValid).value().toBool(defaultValue: false)) {
460 if (!doc.snapshot.validDocVersion || *doc.snapshot.validDocVersion < version) {
461 DomItem vDoc = m_validEnv.path(p);
462 doc.snapshot.validDocVersion = version;
463 doc.snapshot.validDoc = vDoc;
464 } else {
465 qCWarning(lspServerLog) << "skippig update of valid doc to obsolete version"
466 << version << "of document" << QString::fromUtf8(ba: url);
467 }
468 } else {
469 qCWarning(lspServerLog)
470 << "avoid update of validDoc to " << version << "of document"
471 << QString::fromUtf8(ba: url) << "as it is invalid";
472 }
473 }
474 }
475 if (codeModelLog().isDebugEnabled()) {
476 qCDebug(codeModelLog) << "finished update doc of " << url << "to version" << version;
477 snapshotByUrl(url).dump(qDebug() << "postSnapshot",
478 dump: OpenDocumentSnapshot::DumpOption::AllCode);
479 }
480 // we should update the scope in the future thus call addOpen(url)
481 emit updatedSnapshot(url);
482}
483
484void QQmlCodeModel::closeOpenFile(const QByteArray &url)
485{
486 QMutexLocker l(&m_mutex);
487 m_openDocuments.remove(key: url);
488}
489
490void QQmlCodeModel::setRootUrls(const QList<QByteArray> &urls)
491{
492 QMutexLocker l(&m_mutex);
493 m_rootUrls = urls;
494}
495
496void QQmlCodeModel::addRootUrls(const QList<QByteArray> &urls)
497{
498 QMutexLocker l(&m_mutex);
499 for (const QByteArray &url : urls) {
500 if (!m_rootUrls.contains(t: url))
501 m_rootUrls.append(t: url);
502 }
503}
504
505void QQmlCodeModel::removeRootUrls(const QList<QByteArray> &urls)
506{
507 QMutexLocker l(&m_mutex);
508 for (const QByteArray &url : urls)
509 m_rootUrls.removeOne(t: url);
510}
511
512QList<QByteArray> QQmlCodeModel::rootUrls() const
513{
514 QMutexLocker l(&m_mutex);
515 return m_rootUrls;
516}
517
518QStringList QQmlCodeModel::buildPathsForRootUrl(const QByteArray &url)
519{
520 QMutexLocker l(&m_mutex);
521 return m_buildPathsForRootUrl.value(key: url);
522}
523
524static bool isNotSeparator(char c)
525{
526 return c != '/';
527}
528
529QStringList QQmlCodeModel::buildPathsForFileUrl(const QByteArray &url)
530{
531 QList<QByteArray> roots;
532 {
533 QMutexLocker l(&m_mutex);
534 roots = m_buildPathsForRootUrl.keys();
535 }
536 // we want to longest match to be first, as it should override shorter matches
537 std::sort(first: roots.begin(), last: roots.end(), comp: [](const QByteArray &el1, const QByteArray &el2) {
538 if (el1.size() > el2.size())
539 return true;
540 if (el1.size() < el2.size())
541 return false;
542 return el1 < el2;
543 });
544 QStringList buildPaths;
545 QStringList defaultValues;
546 if (!roots.isEmpty() && roots.last().isEmpty())
547 roots.removeLast();
548 QByteArray urlSlash(url);
549 if (!urlSlash.isEmpty() && isNotSeparator(c: urlSlash.at(i: urlSlash.size() - 1)))
550 urlSlash.append(c: '/');
551 // look if the file has a know prefix path
552 for (const QByteArray &root : roots) {
553 if (urlSlash.startsWith(bv: root)) {
554 buildPaths += buildPathsForRootUrl(url: root);
555 break;
556 }
557 }
558 QString path = url2Path(url);
559
560 // fallback to the empty root, if is has an entry.
561 // This is the buildPath that is passed to qmlls via --build-dir.
562 if (buildPaths.isEmpty()) {
563 buildPaths += buildPathsForRootUrl(url: QByteArray());
564 }
565
566 // look in the QMLLS_BUILD_DIRS environment variable
567 if (buildPaths.isEmpty()) {
568 QStringList envPaths = qEnvironmentVariable(varName: "QMLLS_BUILD_DIRS")
569 .split(sep: QDir::listSeparator(), behavior: Qt::SkipEmptyParts);
570 buildPaths += envPaths;
571 }
572
573 // look in the settings.
574 // This is the one that is passed via the .qmlls.ini file.
575 if (buildPaths.isEmpty() && m_settings) {
576 m_settings->search(path);
577 QString buildDir = QStringLiteral(u"buildDir");
578 if (m_settings->isSet(name: buildDir))
579 buildPaths += m_settings->value(name: buildDir).toString().split(sep: QDir::listSeparator(),
580 behavior: Qt::SkipEmptyParts);
581 }
582
583 // heuristic to find build directory
584 if (buildPaths.isEmpty()) {
585 QDir d(path);
586 d.setNameFilters(QStringList({ u"build*"_s }));
587 const int maxDirDepth = 8;
588 int iDir = maxDirDepth;
589 QString dirName = d.dirName();
590 QDateTime lastModified;
591 while (d.cdUp() && --iDir > 0) {
592 for (const QFileInfo &fInfo : d.entryInfoList(filters: QDir::Dirs)) {
593 if (fInfo.completeBaseName() == u"build"
594 || fInfo.completeBaseName().startsWith(s: u"build-%1"_s.arg(a: dirName))) {
595 if (iDir > 1)
596 iDir = 1;
597 if (!lastModified.isValid() || lastModified < fInfo.lastModified()) {
598 buildPaths.clear();
599 buildPaths.append(t: fInfo.absoluteFilePath());
600 }
601 }
602 }
603 }
604 }
605 // add dependent build directories
606 QStringList res;
607 std::reverse(first: buildPaths.begin(), last: buildPaths.end());
608 const int maxDeps = 4;
609 while (!buildPaths.isEmpty()) {
610 QString bPath = buildPaths.last();
611 buildPaths.removeLast();
612 res += bPath;
613 if (QFile::exists(fileName: bPath + u"/_deps") && bPath.split(sep: u"/_deps/"_s).size() < maxDeps) {
614 QDir d(bPath + u"/_deps");
615 for (const QFileInfo &fInfo : d.entryInfoList(filters: QDir::Dirs))
616 buildPaths.append(t: fInfo.absoluteFilePath());
617 }
618 }
619 return res;
620}
621
622void QQmlCodeModel::setBuildPathsForRootUrl(QByteArray url, const QStringList &paths)
623{
624 QMutexLocker l(&m_mutex);
625 if (!url.isEmpty() && isNotSeparator(c: url.at(i: url.size() - 1)))
626 url.append(c: '/');
627 if (paths.isEmpty())
628 m_buildPathsForRootUrl.remove(key: url);
629 else
630 m_buildPathsForRootUrl.insert(key: url, value: paths);
631}
632
633void QQmlCodeModel::openUpdate(const QByteArray &url)
634{
635 bool updateDoc = false;
636 bool updateScope = false;
637 std::optional<int> rNow = 0;
638 QString docText;
639 DomItem validDoc;
640 std::shared_ptr<Utils::TextDocument> document;
641 {
642 QMutexLocker l(&m_mutex);
643 OpenDocument &doc = m_openDocuments[url];
644 document = doc.textDocument;
645 if (!document)
646 return;
647 {
648 QMutexLocker l2(document->mutex());
649 rNow = document->version();
650 }
651 if (rNow && (!doc.snapshot.docVersion || *doc.snapshot.docVersion != *rNow))
652 updateDoc = true;
653 else if (doc.snapshot.validDocVersion
654 && (!doc.snapshot.scopeVersion
655 || *doc.snapshot.scopeVersion != *doc.snapshot.validDocVersion))
656 updateScope = true;
657 else
658 return;
659 if (updateDoc) {
660 QMutexLocker l2(doc.textDocument->mutex());
661 rNow = doc.textDocument->version();
662 docText = doc.textDocument->toPlainText();
663 } else {
664 validDoc = doc.snapshot.validDoc;
665 rNow = doc.snapshot.validDocVersion;
666 }
667 }
668 if (updateDoc) {
669 newDocForOpenFile(url, version: *rNow, docText);
670 }
671 if (updateScope) {
672 // to do
673 }
674}
675
676void QQmlCodeModel::addOpenToUpdate(const QByteArray &url)
677{
678 QMutexLocker l(&m_mutex);
679 m_openDocumentsToUpdate.insert(value: url);
680}
681
682QDebug OpenDocumentSnapshot::dump(QDebug dbg, DumpOptions options)
683{
684 dbg.noquote().nospace() << "{";
685 dbg << " url:" << QString::fromUtf8(ba: url) << "\n";
686 dbg << " docVersion:" << (docVersion ? QString::number(*docVersion) : u"*none*"_s) << "\n";
687 if (options & DumpOption::LatestCode) {
688 dbg << " doc: ------------\n"
689 << doc.field(name: Fields::code).value().toString() << "\n==========\n";
690 } else {
691 dbg << u" doc:"
692 << (doc ? u"%1chars"_s.arg(a: doc.field(name: Fields::code).value().toString().size())
693 : u"*none*"_s)
694 << "\n";
695 }
696 dbg << " validDocVersion:"
697 << (validDocVersion ? QString::number(*validDocVersion) : u"*none*"_s) << "\n";
698 if (options & DumpOption::ValidCode) {
699 dbg << " validDoc: ------------\n"
700 << validDoc.field(name: Fields::code).value().toString() << "\n==========\n";
701 } else {
702 dbg << u" validDoc:"
703 << (validDoc ? u"%1chars"_s.arg(a: validDoc.field(name: Fields::code).value().toString().size())
704 : u"*none*"_s)
705 << "\n";
706 }
707 dbg << " scopeVersion:" << (scopeVersion ? QString::number(*scopeVersion) : u"*none*"_s)
708 << "\n";
709 dbg << " scopeDependenciesLoadTime:" << scopeDependenciesLoadTime << "\n";
710 dbg << " scopeDependenciesChanged" << scopeDependenciesChanged << "\n";
711 dbg << "}";
712 return dbg;
713}
714
715} // namespace QmlLsp
716
717QT_END_NAMESPACE
718

source code of qtdeclarative/src/qmlls/qqmlcodemodel.cpp