Automatic qmlls support (experimental)

Looks for qmlls (the qml language server of Qt) and if available
and set in the preferences uses it instead of the embedded code
model for the supported features.

Its usage is driven by two flags that can be set in the QtQuick > QML/JS
Editing preferences: "use qmlls" activates the use of qmlls if available;
"use latest qmlls" always uses the qmlls of the latest Qt, instead of
the one of the target (with the one used to compile QtCreator as
fallback).

To support disabling/enabling of qmlls as soon as one changes the
preferences the singleton QmllsSettingsManager can emit a signal
on changes.
It also keeps track of the latest qmlls binary known.

QmlJS::ModelmanagerInterface::ProjectInfo is also extended to keep track
of the qmlls binary.

QmlJSEditorDocument uses the ProjectInfo and QmllsSettingsManager to
decide if a LanguageClient::Client should be started for that
document.

The client uses the QmllsClient subclass to keep track of the path of
the qmlls clients and use the same qmlls process or all files that
use the same binary.

Currently qmlls <6.4.0 are not considered because they might have too
many issues.

The enabling/disabling of warnings and highlight is a bit cumbersome
because they are handled together in the semantic highlighter, but
must be handled separately depending on the qmlls capabilities.

The disabling is done at the latest moment stopping the visualization
of the embedded model warnings/highlights/suggestions.
The computation of the semantic info is not suppressed to support the
other features (find usages, semantic highlighting if active,...).
When qmlls supports more features a complete removal of the semantic
info construction could be evaluated.

Change-Id: I3487e1680841025cabba6b339fbfe820ef83f858
Reviewed-by: David Schulz <david.schulz@qt.io>
This commit is contained in:
Fawzi Mohamed
2022-09-28 11:16:49 +02:00
parent 634ac23af7
commit 3baf305f9b
17 changed files with 575 additions and 44 deletions

View File

@@ -12,6 +12,8 @@
#include "qmljssemantichighlighter.h"
#include "qmljssemanticinfoupdater.h"
#include "qmljstextmark.h"
#include "qmllsclient.h"
#include "qmllssettings.h"
#include "qmloutlinemodel.h"
#include <coreplugin/coreconstants.h>
@@ -24,7 +26,10 @@
#include <utils/fileutils.h>
#include <utils/infobar.h>
#include <languageclient/languageclientmanager.h>
#include <QDebug>
#include <QLoggingCategory>
#include <QTextCodec>
const char QML_UI_FILE_WARNING[] = "QmlJSEditor.QmlUiFileWarning";
@@ -36,6 +41,8 @@ using namespace QmlJSTools;
namespace {
Q_LOGGING_CATEGORY(qmllsLog, "qtc.qmlls.editor", QtWarningMsg);
enum {
UPDATE_DOCUMENT_DEFAULT_INTERVAL = 100,
UPDATE_OUTLINE_INTERVAL = 500
@@ -471,6 +478,10 @@ QmlJSEditorDocumentPrivate::QmlJSEditorDocumentPrivate(QmlJSEditorDocument *pare
this, &QmlJSEditorDocumentPrivate::reparseDocument);
connect(modelManager, &ModelManagerInterface::documentUpdated,
this, &QmlJSEditorDocumentPrivate::onDocumentUpdated);
connect(QmllsSettingsManager::instance(),
&QmllsSettingsManager::settingsChanged,
this,
&Internal::QmlJSEditorDocumentPrivate::settingsChanged);
// semantic info
m_semanticInfoUpdater = new SemanticInfoUpdater(this);
@@ -493,6 +504,7 @@ QmlJSEditorDocumentPrivate::QmlJSEditorDocumentPrivate(QmlJSEditorDocument *pare
this, &QmlJSEditorDocumentPrivate::updateOutlineModel);
modelManager->updateSourceFiles(Utils::FilePaths({parent->filePath()}), false);
settingsChanged();
}
QmlJSEditorDocumentPrivate::~QmlJSEditorDocumentPrivate()
@@ -530,7 +542,8 @@ void QmlJSEditorDocumentPrivate::onDocumentUpdated(Document::Ptr doc)
// got a correctly parsed (or recovered) file.
m_semanticInfoDocRevision = doc->editorRevision();
m_semanticInfoUpdater->update(doc, ModelManagerInterface::instance()->snapshot());
} else if (doc->language().isFullySupportedLanguage()) {
} else if (doc->language().isFullySupportedLanguage()
&& m_qmllsStatus.semanticWarningsSource == QmllsStatus::Source::EmbeddedCodeModel) {
createTextMarks(doc->diagnosticMessages());
}
emit q->updateCodeWarnings(doc);
@@ -567,7 +580,8 @@ void QmlJSEditorDocumentPrivate::acceptNewSemanticInfo(const SemanticInfo &seman
m_outlineModelNeedsUpdate = true;
m_semanticHighlightingNecessary = true;
createTextMarks(m_semanticInfo);
if (m_qmllsStatus.semanticWarningsSource == QmllsStatus::Source::EmbeddedCodeModel)
createTextMarks(m_semanticInfo);
emit q->semanticInfoUpdated(m_semanticInfo); // calls triggerPendingUpdates as necessary
}
@@ -594,6 +608,8 @@ static void cleanMarks(QVector<TextEditor::TextMark *> *marks, TextEditor::TextD
void QmlJSEditorDocumentPrivate::createTextMarks(const QList<DiagnosticMessage> &diagnostics)
{
if (m_qmllsStatus.semanticWarningsSource != QmllsStatus::Source::EmbeddedCodeModel)
return;
for (const DiagnosticMessage &diagnostic : diagnostics) {
const auto onMarkRemoved = [this](QmlJSTextMark *mark) {
m_diagnosticMarks.removeAll(mark);
@@ -637,6 +653,158 @@ void QmlJSEditorDocumentPrivate::cleanSemanticMarks()
cleanMarks(&m_semanticMarks, q);
}
void QmlJSEditorDocumentPrivate::setSemanticWarningSource(QmllsStatus::Source newSource)
{
if (m_qmllsStatus.semanticWarningsSource == newSource)
return;
m_qmllsStatus.semanticWarningsSource = newSource;
QTC_ASSERT(q->thread() == QThread::currentThread(), return );
switch (m_qmllsStatus.semanticWarningsSource) {
case QmllsStatus::Source::Qmlls:
m_semanticHighlighter->setEnableWarnings(false);
cleanDiagnosticMarks();
cleanSemanticMarks();
if (!q->isSemanticInfoOutdated()) {
// clean up underlines for warning messages
m_semanticHighlightingNecessary = false;
m_semanticHighlighter->rerun(m_semanticInfo);
}
break;
case QmllsStatus::Source::EmbeddedCodeModel:
m_semanticHighlighter->setEnableWarnings(true);
reparseDocument();
break;
}
}
void QmlJSEditorDocumentPrivate::setSemanticHighlightSource(QmllsStatus::Source newSource)
{
if (m_qmllsStatus.semanticHighlightSource == newSource)
return;
m_qmllsStatus.semanticHighlightSource = newSource;
QTC_ASSERT(q->thread() == QThread::currentThread(), return );
switch (m_qmllsStatus.semanticHighlightSource) {
case QmllsStatus::Source::Qmlls:
m_semanticHighlighter->setEnableHighlighting(false);
cleanSemanticMarks();
break;
case QmllsStatus::Source::EmbeddedCodeModel:
m_semanticHighlighter->setEnableHighlighting(true);
if (!q->isSemanticInfoOutdated()) {
m_semanticHighlightingNecessary = false;
m_semanticHighlighter->rerun(m_semanticInfo);
}
break;
}
}
void QmlJSEditorDocumentPrivate::setCompletionSource(QmllsStatus::Source newSource)
{
if (m_qmllsStatus.completionSource == newSource)
return;
m_qmllsStatus.completionSource = newSource;
switch (m_qmllsStatus.completionSource) {
case QmllsStatus::Source::Qmlls:
// activation of the document already takes care of setting it
break;
case QmllsStatus::Source::EmbeddedCodeModel:
// deactivation of the document takes care of restoring it
break;
}
}
void QmlJSEditorDocumentPrivate::setSourcesWithCapabilities(
const LanguageServerProtocol::ServerCapabilities &cap)
{
if (cap.completionProvider())
setCompletionSource(QmllsStatus::Source::Qmlls);
else
setCompletionSource(QmllsStatus::Source::EmbeddedCodeModel);
if (cap.codeActionProvider())
setSemanticWarningSource(QmllsStatus::Source::Qmlls);
else
setSemanticWarningSource(QmllsStatus::Source::EmbeddedCodeModel);
if (cap.semanticTokensProvider())
setSemanticHighlightSource(QmllsStatus::Source::Qmlls);
else
setSemanticHighlightSource(QmllsStatus::Source::EmbeddedCodeModel);
}
static Utils::FilePath qmllsForFile(const Utils::FilePath &file,
QmlJS::ModelManagerInterface *modelManager)
{
QmllsSettingsManager *settingsManager = QmllsSettingsManager::instance();
QmllsSettings settings = settingsManager->lastSettings();
bool enabled = settings.useQmlls;
if (!enabled)
return Utils::FilePath();
if (settings.useLatestQmlls)
return settingsManager->latestQmlls();
QmlJS::ModelManagerInterface::ProjectInfo pInfo = modelManager->projectInfoForPath(file);
return pInfo.qmllsPath;
}
void QmlJSEditorDocumentPrivate::settingsChanged()
{
Utils::FilePath newQmlls = qmllsForFile(q->filePath(), ModelManagerInterface::instance());
if (m_qmllsStatus.qmllsPath == newQmlls)
return;
m_qmllsStatus.qmllsPath = newQmlls;
auto lspClientManager = LanguageClient::LanguageClientManager::instance();
if (newQmlls.isEmpty()) {
qCDebug(qmllsLog) << "disabling qmlls for" << q->filePath();
if (LanguageClient::Client *client = lspClientManager->clientForDocument(q)) {
qCDebug(qmllsLog) << "deactivating " << q->filePath() << "in qmlls" << newQmlls;
client->deactivateDocument(q);
} else
qCWarning(qmllsLog) << "Could not find client to disable for document " << q->filePath()
<< " in LanguageClient::LanguageClientManager";
setCompletionSource(QmllsStatus::Source::EmbeddedCodeModel);
setSemanticWarningSource(QmllsStatus::Source::EmbeddedCodeModel);
setSemanticHighlightSource(QmllsStatus::Source::EmbeddedCodeModel);
} else if (QmllsClient *client = QmllsClient::clientForQmlls(newQmlls)) {
bool shouldActivate = false;
if (auto oldClient = lspClientManager->clientForDocument(q)) {
// check if it was disabled
if (client == oldClient)
shouldActivate = true;
}
switch (client->state()) {
case LanguageClient::Client::State::Uninitialized:
case LanguageClient::Client::State::InitializeRequested:
connect(client,
&LanguageClient::Client::initialized,
this,
&QmlJSEditorDocumentPrivate::setSourcesWithCapabilities);
break;
case LanguageClient::Client::State::Initialized:
setSourcesWithCapabilities(client->capabilities());
break;
case LanguageClient::Client::State::Error:
qCWarning(qmllsLog) << "qmlls" << newQmlls << "requested for document" << q->filePath()
<< "had errors, skipping setSourcesWithCababilities";
break;
case LanguageClient::Client::State::Shutdown:
qCWarning(qmllsLog) << "qmlls" << newQmlls << "requested for document" << q->filePath()
<< "did stop, skipping setSourcesWithCababilities";
break;
case LanguageClient::Client::State::ShutdownRequested:
qCWarning(qmllsLog) << "qmlls" << newQmlls << "requested for document" << q->filePath()
<< "is stopping, skipping setSourcesWithCababilities";
break;
}
if (shouldActivate) {
qCDebug(qmllsLog) << "reactivating " << q->filePath() << "in qmlls" << newQmlls;
client->activateDocument(q);
} else {
qCDebug(qmllsLog) << "opening " << q->filePath() << "in qmlls" << newQmlls;
lspClientManager->openDocumentWithClient(q, client);
}
} else {
qCWarning(qmllsLog) << "could not start qmlls " << newQmlls << "for" << q->filePath();
}
}
} // Internal
QmlJSEditorDocument::QmlJSEditorDocument(Utils::Id id)