Files
qt-creator/src/plugins/qmljseditor/qmljseditordocument.cpp
Semih Yavuz df2e55d92a Opt out qmlls semantic highlighting
Apperantly, qmlls semantic highlighting is not as powerful as the
embedded code model's highlighter. Disable lsp based highlighting until
qmlls semantic highlighting reaches the feature parity with the QtC
highlighter. This is tracked by QTBUG-126550.

Task-number: QTBUG-126550
Fixes: QTCREATORBUG-31083
Change-Id: I6cab94d4ba1d1cde50b62ce03a6b1cc5b6d7d27b
Reviewed-by: Sami Shalayel <sami.shalayel@qt.io>
Reviewed-by: David Schulz <david.schulz@qt.io>
2024-06-24 07:35:03 +00:00

926 lines
29 KiB
C++

// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "qmljseditordocument.h"
#include "qmljseditordocument_p.h"
#include "qmljseditorplugin.h"
#include "qmljseditortr.h"
#include "qmljshighlighter.h"
#include "qmljsquickfixassist.h"
#include "qmljssemantichighlighter.h"
#include "qmljssemanticinfoupdater.h"
#include "qmljstextmark.h"
#include "qmllsclient.h"
#include "qmllssettings.h"
#include "qmloutlinemodel.h"
#include <coreplugin/coreconstants.h>
#include <coreplugin/modemanager.h>
#include <qmljstools/qmljsindenter.h>
#include <qmljstools/qmljsmodelmanager.h>
#include <qmljstools/qmljsqtstylecodeformatter.h>
#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";
using namespace QmlJSEditor;
using namespace QmlJS;
using namespace QmlJS::AST;
using namespace QmlJSTools;
namespace {
Q_LOGGING_CATEGORY(qmllsLog, "qtc.qmlls.editor", QtWarningMsg);
enum {
UPDATE_DOCUMENT_DEFAULT_INTERVAL = 100,
UPDATE_OUTLINE_INTERVAL = 500
};
struct Declaration
{
QString text;
int startLine = 0;
int startColumn = 0;
int endLine = 0;
int endColumn = 0;
};
class FindIdDeclarations: protected Visitor
{
public:
using Result = QHash<QString, QList<SourceLocation> >;
Result operator()(Document::Ptr doc)
{
_ids.clear();
_maybeIds.clear();
if (doc && doc->qmlProgram())
doc->qmlProgram()->accept(this);
return _ids;
}
protected:
QString asString(AST::UiQualifiedId *id)
{
QString text;
for (; id; id = id->next) {
if (!id->name.isEmpty())
text += id->name.toString();
else
text += QLatin1Char('?');
if (id->next)
text += QLatin1Char('.');
}
return text;
}
void accept(AST::Node *node) { AST::Node::accept(node, this); }
using Visitor::visit;
using Visitor::endVisit;
bool visit(AST::UiScriptBinding *node) override
{
if (asString(node->qualifiedId) == QLatin1String("id")) {
if (auto stmt = AST::cast<const AST::ExpressionStatement*>(node->statement)) {
if (auto idExpr = AST::cast<const AST::IdentifierExpression *>(stmt->expression)) {
if (!idExpr->name.isEmpty()) {
const QString &id = idExpr->name.toString();
QList<SourceLocation> *locs = &_ids[id];
locs->append(idExpr->firstSourceLocation());
locs->append(_maybeIds.value(id));
_maybeIds.remove(id);
return false;
}
}
}
}
accept(node->statement);
return false;
}
bool visit(AST::IdentifierExpression *node) override
{
if (!node->name.isEmpty()) {
const QString &name = node->name.toString();
if (_ids.contains(name))
_ids[name].append(node->identifierToken);
else
_maybeIds[name].append(node->identifierToken);
}
return false;
}
void throwRecursionDepthError() override
{
qWarning("Warning: Hit maximum recursion depth while visiting AST in FindIdDeclarations");
}
private:
Result _ids;
Result _maybeIds;
};
class FindDeclarations: protected Visitor
{
QList<Declaration> _declarations;
int _depth;
public:
QList<Declaration> operator()(AST::Node *node)
{
_depth = -1;
_declarations.clear();
accept(node);
return _declarations;
}
protected:
using Visitor::visit;
using Visitor::endVisit;
QString asString(AST::UiQualifiedId *id)
{
QString text;
for (; id; id = id->next) {
if (!id->name.isEmpty())
text += id->name.toString();
else
text += QLatin1Char('?');
if (id->next)
text += QLatin1Char('.');
}
return text;
}
void accept(AST::Node *node) { AST::Node::accept(node, this); }
void init(Declaration *decl, AST::UiObjectMember *member)
{
const SourceLocation first = member->firstSourceLocation();
const SourceLocation last = member->lastSourceLocation();
decl->startLine = first.startLine;
decl->startColumn = first.startColumn;
decl->endLine = last.startLine;
decl->endColumn = last.startColumn + last.length;
}
void init(Declaration *decl, AST::ExpressionNode *expressionNode)
{
const SourceLocation first = expressionNode->firstSourceLocation();
const SourceLocation last = expressionNode->lastSourceLocation();
decl->startLine = first.startLine;
decl->startColumn = first.startColumn;
decl->endLine = last.startLine;
decl->endColumn = last.startColumn + last.length;
}
bool visit(AST::UiObjectDefinition *node) override
{
++_depth;
Declaration decl;
init(&decl, node);
decl.text.fill(QLatin1Char(' '), _depth);
if (node->qualifiedTypeNameId)
decl.text.append(asString(node->qualifiedTypeNameId));
else
decl.text.append(QLatin1Char('?'));
_declarations.append(decl);
return true; // search for more bindings
}
void endVisit(AST::UiObjectDefinition *) override
{
--_depth;
}
bool visit(AST::UiObjectBinding *node) override
{
++_depth;
Declaration decl;
init(&decl, node);
decl.text.fill(QLatin1Char(' '), _depth);
decl.text.append(asString(node->qualifiedId));
decl.text.append(QLatin1String(": "));
if (node->qualifiedTypeNameId)
decl.text.append(asString(node->qualifiedTypeNameId));
else
decl.text.append(QLatin1Char('?'));
_declarations.append(decl);
return true; // search for more bindings
}
void endVisit(AST::UiObjectBinding *) override
{
--_depth;
}
bool visit(AST::UiScriptBinding *) override
{
++_depth;
#if 0 // ### ignore script bindings for now.
Declaration decl;
init(&decl, node);
decl.text.fill(QLatin1Char(' '), _depth);
decl.text.append(asString(node->qualifiedId));
_declarations.append(decl);
#endif
return false; // more more bindings in this subtree.
}
void endVisit(AST::UiScriptBinding *) override
{
--_depth;
}
bool visit(AST::TemplateLiteral *ast) override
{
// avoid? finds function declarations in templates
AST::Node::accept(ast->expression, this);
return true;
}
bool visit(AST::FunctionExpression *) override
{
return false;
}
bool visit(AST::FunctionDeclaration *ast) override
{
if (ast->name.isEmpty())
return false;
Declaration decl;
init(&decl, ast);
decl.text.fill(QLatin1Char(' '), _depth);
decl.text += ast->name.toString();
decl.text += QLatin1Char('(');
for (FormalParameterList *it = ast->formals; it; it = it->next) {
if (!it->element->bindingIdentifier.isEmpty())
decl.text += it->element->bindingIdentifier.toString();
if (it->next)
decl.text += QLatin1String(", ");
}
decl.text += QLatin1Char(')');
_declarations.append(decl);
return false;
}
bool visit(AST::PatternElement *ast) override
{
if (!ast->isVariableDeclaration() || ast->bindingIdentifier.isEmpty())
return false;
Declaration decl;
decl.text.fill(QLatin1Char(' '), _depth);
decl.text += ast->bindingIdentifier.toString();
const SourceLocation first = ast->identifierToken;
decl.startLine = first.startLine;
decl.startColumn = first.startColumn;
decl.endLine = first.startLine;
decl.endColumn = first.startColumn + first.length;
_declarations.append(decl);
return false;
}
bool visit(AST::BinaryExpression *ast) override
{
auto field = AST::cast<const AST::FieldMemberExpression *>(ast->left);
auto funcExpr = AST::cast<const AST::FunctionExpression *>(ast->right);
if (field && funcExpr && funcExpr->body && (ast->op == QSOperator::Assign)) {
Declaration decl;
init(&decl, ast);
decl.text.fill(QLatin1Char(' '), _depth);
decl.text += field->name.toString();
decl.text += QLatin1Char('(');
for (FormalParameterList *it = funcExpr->formals; it; it = it->next) {
if (!it->element->bindingIdentifier.isEmpty())
decl.text += it->element->bindingIdentifier.toString();
if (it->next)
decl.text += QLatin1String(", ");
}
decl.text += QLatin1Char(')');
_declarations.append(decl);
}
return true;
}
};
class CreateRanges: protected AST::Visitor
{
QTextDocument *_textDocument;
QList<Range> _ranges;
public:
QList<Range> operator()(QTextDocument *textDocument, Document::Ptr doc)
{
_textDocument = textDocument;
_ranges.clear();
if (doc && doc->ast() != nullptr)
doc->ast()->accept(this);
return _ranges;
}
protected:
using AST::Visitor::visit;
bool visit(AST::UiObjectBinding *ast) override
{
if (ast->initializer && ast->initializer->lbraceToken.length)
_ranges.append(createRange(ast, ast->initializer));
return true;
}
bool visit(AST::UiObjectDefinition *ast) override
{
if (ast->initializer && ast->initializer->lbraceToken.length)
_ranges.append(createRange(ast, ast->initializer));
return true;
}
bool visit(AST::FunctionExpression *ast) override
{
_ranges.append(createRange(ast));
return true;
}
bool visit(AST::TemplateLiteral *ast) override
{
AST::Node::accept(ast->expression, this);
return true;
}
bool visit(AST::FunctionDeclaration *ast) override
{
_ranges.append(createRange(ast));
return true;
}
bool visit(AST::BinaryExpression *ast) override
{
auto field = AST::cast<AST::FieldMemberExpression *>(ast->left);
auto funcExpr = AST::cast<AST::FunctionExpression *>(ast->right);
if (field && funcExpr && funcExpr->body && (ast->op == QSOperator::Assign))
_ranges.append(createRange(ast, ast->firstSourceLocation(), ast->lastSourceLocation()));
return true;
}
bool visit(AST::UiScriptBinding *ast) override
{
if (auto block = AST::cast<AST::Block *>(ast->statement))
_ranges.append(createRange(ast, block));
return true;
}
void throwRecursionDepthError() override
{
qWarning("Warning: Hit maximum recursion depth while visiting AST in CreateRanges");
}
Range createRange(AST::UiObjectMember *member, AST::UiObjectInitializer *ast)
{
return createRange(member, member->firstSourceLocation(), ast->rbraceToken);
}
Range createRange(AST::FunctionExpression *ast)
{
return createRange(ast, ast->lbraceToken, ast->rbraceToken);
}
Range createRange(AST::UiScriptBinding *ast, AST::Block *block)
{
return createRange(ast, block->lbraceToken, block->rbraceToken);
}
Range createRange(AST::Node *ast, SourceLocation start, SourceLocation end)
{
Range range;
range.ast = ast;
range.begin = QTextCursor(_textDocument);
range.begin.setPosition(start.begin());
range.end = QTextCursor(_textDocument);
range.end.setPosition(end.end());
return range;
}
};
}
namespace QmlJSEditor {
namespace Internal {
QmlJSEditorDocumentPrivate::QmlJSEditorDocumentPrivate(QmlJSEditorDocument *parent)
: q(parent)
, m_semanticHighlighter(new SemanticHighlighter(parent))
, m_outlineModel(new QmlOutlineModel(parent))
{
ModelManagerInterface *modelManager = ModelManagerInterface::instance();
// code model
m_updateDocumentTimer.setInterval(UPDATE_DOCUMENT_DEFAULT_INTERVAL);
m_updateDocumentTimer.setSingleShot(true);
connect(q->document(), &QTextDocument::contentsChanged,
&m_updateDocumentTimer, QOverload<>::of(&QTimer::start));
connect(&m_updateDocumentTimer, &QTimer::timeout,
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();
connect(m_semanticInfoUpdater, &SemanticInfoUpdater::finished,
m_semanticInfoUpdater, &QObject::deleteLater);
connect(m_semanticInfoUpdater, &SemanticInfoUpdater::updated,
this, &QmlJSEditorDocumentPrivate::acceptNewSemanticInfo);
m_semanticInfoUpdater->start();
// library info changes
m_reupdateSemanticInfoTimer.setInterval(UPDATE_DOCUMENT_DEFAULT_INTERVAL);
m_reupdateSemanticInfoTimer.setSingleShot(true);
connect(&m_reupdateSemanticInfoTimer, &QTimer::timeout,
this, &QmlJSEditorDocumentPrivate::reupdateSemanticInfo);
connect(modelManager, &ModelManagerInterface::libraryInfoUpdated,
&m_reupdateSemanticInfoTimer, QOverload<>::of(&QTimer::start));
// outline model
m_updateOutlineModelTimer.setInterval(UPDATE_OUTLINE_INTERVAL);
m_updateOutlineModelTimer.setSingleShot(true);
connect(&m_updateOutlineModelTimer, &QTimer::timeout,
this, &QmlJSEditorDocumentPrivate::updateOutlineModel);
modelManager->updateSourceFiles(Utils::FilePaths({parent->filePath()}), false);
}
QmlJSEditorDocumentPrivate::~QmlJSEditorDocumentPrivate()
{
m_semanticInfoUpdater->abort();
// clean up all marks, otherwise a callback could try to access deleted members.
// see QTCREATORBUG-20199
cleanDiagnosticMarks();
cleanSemanticMarks();
}
void QmlJSEditorDocumentPrivate::invalidateFormatterCache()
{
CreatorCodeFormatter formatter(q->tabSettings());
formatter.invalidateCache(q->document());
}
void QmlJSEditorDocumentPrivate::reparseDocument()
{
ModelManagerInterface::instance()->updateSourceFiles(Utils::FilePaths({q->filePath()}), false);
}
void QmlJSEditorDocumentPrivate::onDocumentUpdated(Document::Ptr doc)
{
if (q->filePath() != doc->fileName())
return;
// text document has changed, simply wait for the next onDocumentUpdated
if (doc->editorRevision() != q->document()->revision())
return;
cleanDiagnosticMarks();
if (doc->ast()) {
// got a correctly parsed (or recovered) file.
m_semanticInfoDocRevision = doc->editorRevision();
m_semanticInfoUpdater->update(doc, ModelManagerInterface::instance()->snapshot());
} else if (doc->language().isFullySupportedLanguage()
&& m_qmllsStatus.semanticWarningsSource == QmllsStatus::Source::EmbeddedCodeModel) {
createTextMarks(doc->diagnosticMessages());
}
emit q->updateCodeWarnings(doc);
}
void QmlJSEditorDocumentPrivate::reupdateSemanticInfo()
{
// If the editor is newer than the semantic info (possibly with update in progress),
// new semantic infos won't be accepted anyway. We'll get a onDocumentUpdated anyhow.
if (q->document()->revision() != m_semanticInfoDocRevision)
return;
m_semanticInfoUpdater->reupdate(ModelManagerInterface::instance()->snapshot());
}
void QmlJSEditorDocumentPrivate::acceptNewSemanticInfo(const SemanticInfo &semanticInfo)
{
if (semanticInfo.revision() != q->document()->revision()) {
// ignore outdated semantic infos
return;
}
m_semanticInfo = semanticInfo;
Document::Ptr doc = semanticInfo.document;
// create the ranges
CreateRanges createRanges;
m_semanticInfo.ranges = createRanges(q->document(), doc);
// Refresh the ids
FindIdDeclarations updateIds;
m_semanticInfo.idLocations = updateIds(doc);
m_outlineModelNeedsUpdate = true;
m_semanticHighlightingNecessary = true;
if (m_qmllsStatus.semanticWarningsSource == QmllsStatus::Source::EmbeddedCodeModel)
createTextMarks(m_semanticInfo);
emit q->semanticInfoUpdated(m_semanticInfo); // calls triggerPendingUpdates as necessary
}
void QmlJSEditorDocumentPrivate::updateOutlineModel()
{
if (isSemanticInfoOutdated())
return; // outline update will be retriggered when semantic info is updated
m_outlineModel->update(m_semanticInfo);
}
bool QmlJSEditorDocumentPrivate::isSemanticInfoOutdated() const
{
return m_semanticInfo.revision() != q->document()->revision();
}
static void cleanMarks(QVector<TextEditor::TextMark *> *marks, TextEditor::TextDocument *doc)
{
// if doc is null, this method is improperly called, so better do nothing that leave an
// inconsistent state where marks are cleared but not removed from doc.
if (!marks || !doc)
return;
for (TextEditor::TextMark *mark : *marks) {
doc->removeMark(mark);
delete mark;
}
marks->clear();
}
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);
delete mark;
};
auto mark = new QmlJSTextMark(q->filePath(), diagnostic, onMarkRemoved);
m_diagnosticMarks.append(mark);
q->addMark(mark);
}
}
void QmlJSEditorDocumentPrivate::cleanDiagnosticMarks()
{
cleanMarks(&m_diagnosticMarks, q);
}
void QmlJSEditorDocumentPrivate::createTextMarks(const SemanticInfo &info)
{
cleanSemanticMarks();
const auto onMarkRemoved = [this](QmlJSTextMark *mark) {
m_semanticMarks.removeAll(mark);
delete mark;
};
for (const DiagnosticMessage &diagnostic : std::as_const(info.semanticMessages)) {
auto mark = new QmlJSTextMark(q->filePath(),
diagnostic, onMarkRemoved);
m_semanticMarks.append(mark);
q->addMark(mark);
}
for (const QmlJS::StaticAnalysis::Message &message : std::as_const(info.staticAnalysisMessages)) {
auto mark = new QmlJSTextMark(q->filePath(),
message, onMarkRemoved);
m_semanticMarks.append(mark);
q->addMark(mark);
}
}
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 (m_semanticInfo.isValid() && !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 (m_semanticInfo.isValid() && !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);
// TODO: uncomment when qmlls semantic tokens reach a stable state
// 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 {};
if (settings.useLatestQmlls)
return settingsManager->latestQmlls();
QmlJS::ModelManagerInterface::ProjectInfo pInfo = modelManager->projectInfoForPath(file);
if (!settings.ignoreMinimumQmllsVersion
&& QVersionNumber::fromString(pInfo.qtVersionString) < settings.mininumQmllsVersion) {
return {};
}
return pInfo.qmllsPath;
}
void QmlJSEditorDocumentPrivate::settingsChanged()
{
if (q->isTemporary())
return;
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::FailedToInitialize:
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)
: d(new Internal::QmlJSEditorDocumentPrivate(this))
{
setId(id);
connect(this, &TextEditor::TextDocument::tabSettingsChanged,
d, &Internal::QmlJSEditorDocumentPrivate::invalidateFormatterCache);
connect(this, &TextEditor::TextDocument::openFinishedSuccessfully,
d, &Internal::QmlJSEditorDocumentPrivate::settingsChanged);
resetSyntaxHighlighter([] { return new QmlJSHighlighter(); });
setCodec(QTextCodec::codecForName("UTF-8")); // qml files are defined to be utf-8
setIndenter(createQmlJsIndenter(document()));
}
bool QmlJSEditorDocument::supportsCodec(const QTextCodec *codec) const
{
return codec == QTextCodec::codecForName("UTF-8");
}
QmlJSEditorDocument::~QmlJSEditorDocument()
{
delete d;
}
const SemanticInfo &QmlJSEditorDocument::semanticInfo() const
{
return d->m_semanticInfo;
}
bool QmlJSEditorDocument::isSemanticInfoOutdated() const
{
return d->isSemanticInfoOutdated();
}
QVector<QTextLayout::FormatRange> QmlJSEditorDocument::diagnosticRanges() const
{
return d->m_diagnosticRanges;
}
Internal::QmlOutlineModel *QmlJSEditorDocument::outlineModel() const
{
return d->m_outlineModel;
}
TextEditor::IAssistProvider *QmlJSEditorDocument::quickFixAssistProvider() const
{
if (const auto baseProvider = TextDocument::quickFixAssistProvider())
return baseProvider;
return Internal::quickFixAssistProvider();
}
void QmlJSEditorDocument::setIsDesignModePreferred(bool value)
{
d->m_isDesignModePreferred = value;
if (value) {
if (infoBar()->canInfoBeAdded(QML_UI_FILE_WARNING)) {
Utils::InfoBarEntry info(QML_UI_FILE_WARNING,
Tr::tr("This file should only be edited in <b>Design</b> mode."));
info.addCustomButton(Tr::tr("Switch Mode"), []() {
Core::ModeManager::activateMode(Core::Constants::MODE_DESIGN);
});
infoBar()->addInfo(info);
}
} else if (infoBar()->containsInfo(QML_UI_FILE_WARNING)) {
infoBar()->removeInfo(QML_UI_FILE_WARNING);
}
}
bool QmlJSEditorDocument::isDesignModePreferred() const
{
return d->m_isDesignModePreferred;
}
void QmlJSEditorDocument::setDiagnosticRanges(const QVector<QTextLayout::FormatRange> &ranges)
{
d->m_diagnosticRanges = ranges;
}
void QmlJSEditorDocument::applyFontSettings()
{
TextDocument::applyFontSettings();
d->m_semanticHighlighter->updateFontSettings(fontSettings());
if (!isSemanticInfoOutdated()) {
d->m_semanticHighlightingNecessary = false;
d->m_semanticHighlighter->rerun(d->m_semanticInfo);
}
}
void QmlJSEditorDocument::triggerPendingUpdates()
{
TextDocument::triggerPendingUpdates(); // calls applyFontSettings if necessary
// might still need to rehighlight if font settings did not change
if (d->m_semanticHighlightingNecessary && !isSemanticInfoOutdated()) {
d->m_semanticHighlightingNecessary = false;
d->m_semanticHighlighter->rerun(d->m_semanticInfo);
}
if (d->m_outlineModelNeedsUpdate && !isSemanticInfoOutdated()) {
d->m_outlineModelNeedsUpdate = false;
d->m_updateOutlineModelTimer.start();
}
}
} // QmlJSEditor