forked from qt-creator/qt-creator
QmlJS: Add semantic highlighting.
Change-Id: If9293244075cff1a52801b50cdbb77248ecd21ea Reviewed-on: http://codereview.qt.nokia.com/3310 Reviewed-by: Fawzi Mohamed <fawzi.mohamed@nokia.com>
This commit is contained in:
@@ -56,6 +56,20 @@ QList<const QmlComponentChain *> QmlComponentChain::instantiatingComponents() co
|
||||
return m_instantiatingComponents;
|
||||
}
|
||||
|
||||
const ObjectValue *QmlComponentChain::idScope() const
|
||||
{
|
||||
if (!m_document)
|
||||
return 0;
|
||||
return m_document->bind()->idEnvironment();
|
||||
}
|
||||
|
||||
const ObjectValue *QmlComponentChain::rootObjectScope() const
|
||||
{
|
||||
if (!m_document)
|
||||
return 0;
|
||||
return m_document->bind()->rootObjectValue();
|
||||
}
|
||||
|
||||
void QmlComponentChain::addInstantiatingComponent(const QmlComponentChain *component)
|
||||
{
|
||||
m_instantiatingComponents.append(component);
|
||||
@@ -188,9 +202,9 @@ static void collectScopes(const QmlComponentChain *chain, QList<const ObjectValu
|
||||
if (!chain->document())
|
||||
return;
|
||||
|
||||
if (ObjectValue *root = chain->document()->bind()->rootObjectValue())
|
||||
if (const ObjectValue *root = chain->rootObjectScope())
|
||||
target->append(root);
|
||||
if (ObjectValue *ids = chain->document()->bind()->idEnvironment())
|
||||
if (const ObjectValue *ids = chain->idScope())
|
||||
target->append(ids);
|
||||
}
|
||||
|
||||
@@ -294,4 +308,3 @@ void ScopeChain::makeComponentChain(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -58,6 +58,9 @@ public:
|
||||
Document::Ptr document() const;
|
||||
QList<const QmlComponentChain *> instantiatingComponents() const;
|
||||
|
||||
const ObjectValue *idScope() const;
|
||||
const ObjectValue *rootObjectScope() const;
|
||||
|
||||
// takes ownership
|
||||
void addInstantiatingComponent(const QmlComponentChain *component);
|
||||
|
||||
|
@@ -442,7 +442,7 @@ bool CheckSymbols::visit(NamespaceAST *ast)
|
||||
if (! tok.generated()) {
|
||||
unsigned line, column;
|
||||
getTokenStartPosition(ast->identifier_token, &line, &column);
|
||||
Use use(line, column, tok.length());
|
||||
Use use(line, column, tok.length(), SemanticInfo::TypeUse);
|
||||
addUse(use);
|
||||
}
|
||||
}
|
||||
@@ -457,7 +457,7 @@ bool CheckSymbols::visit(UsingDirectiveAST *)
|
||||
|
||||
bool CheckSymbols::visit(EnumeratorAST *ast)
|
||||
{
|
||||
addUse(ast->identifier_token, Use::Static);
|
||||
addUse(ast->identifier_token, SemanticInfo::StaticUse);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -469,7 +469,7 @@ bool CheckSymbols::visit(SimpleDeclarationAST *ast)
|
||||
if (NameAST *declId = declaratorId(ast->declarator_list->value)) {
|
||||
if (Function *funTy = decl->type()->asFunctionType()) {
|
||||
if (funTy->isVirtual()) {
|
||||
addUse(declId, Use::VirtualMethod);
|
||||
addUse(declId, SemanticInfo::VirtualMethodUse);
|
||||
} else if (maybeVirtualMethod(decl->name())) {
|
||||
addVirtualMethod(_context.lookup(decl->name(), decl->enclosingScope()), declId, funTy->argumentCount());
|
||||
}
|
||||
@@ -490,7 +490,7 @@ bool CheckSymbols::visit(ElaboratedTypeSpecifierAST *ast)
|
||||
{
|
||||
accept(ast->attribute_list);
|
||||
accept(ast->name);
|
||||
addUse(ast->name, Use::Type);
|
||||
addUse(ast->name, SemanticInfo::TypeUse);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -643,7 +643,7 @@ void CheckSymbols::checkName(NameAST *ast, Scope *scope)
|
||||
if (ast->asDestructorName() != 0) {
|
||||
Class *klass = scope->asClass();
|
||||
if (hasVirtualDestructor(_context.lookupType(klass)))
|
||||
addUse(ast, Use::VirtualMethod);
|
||||
addUse(ast, SemanticInfo::VirtualMethodUse);
|
||||
} else if (maybeType(ast->name) || maybeStatic(ast->name)) {
|
||||
const QList<LookupItem> candidates = _context.lookup(ast->name, scope);
|
||||
addTypeOrStatic(candidates, ast);
|
||||
@@ -693,7 +693,7 @@ bool CheckSymbols::visit(QualifiedNameAST *ast)
|
||||
if (NameAST *class_or_namespace_name = nested_name_specifier->class_or_namespace_name) {
|
||||
if (TemplateIdAST *template_id = class_or_namespace_name->asTemplateId()) {
|
||||
if (template_id->template_token) {
|
||||
addUse(template_id, Use::Type);
|
||||
addUse(template_id, SemanticInfo::TypeUse);
|
||||
binding = 0; // there's no way we can find a binding.
|
||||
}
|
||||
|
||||
@@ -714,7 +714,7 @@ bool CheckSymbols::visit(QualifiedNameAST *ast)
|
||||
if (binding && ast->unqualified_name) {
|
||||
if (ast->unqualified_name->asDestructorName() != 0) {
|
||||
if (hasVirtualDestructor(binding))
|
||||
addUse(ast->unqualified_name, Use::VirtualMethod);
|
||||
addUse(ast->unqualified_name, SemanticInfo::VirtualMethodUse);
|
||||
} else {
|
||||
addTypeOrStatic(binding->find(ast->unqualified_name->name), ast->unqualified_name);
|
||||
}
|
||||
@@ -729,7 +729,7 @@ bool CheckSymbols::visit(QualifiedNameAST *ast)
|
||||
|
||||
bool CheckSymbols::visit(TypenameTypeParameterAST *ast)
|
||||
{
|
||||
addUse(ast->name, Use::Type);
|
||||
addUse(ast->name, SemanticInfo::TypeUse);
|
||||
accept(ast->type_id);
|
||||
return false;
|
||||
}
|
||||
@@ -737,7 +737,7 @@ bool CheckSymbols::visit(TypenameTypeParameterAST *ast)
|
||||
bool CheckSymbols::visit(TemplateTypeParameterAST *ast)
|
||||
{
|
||||
accept(ast->template_parameter_list);
|
||||
addUse(ast->name, Use::Type);
|
||||
addUse(ast->name, SemanticInfo::TypeUse);
|
||||
accept(ast->type_id);
|
||||
return false;
|
||||
}
|
||||
@@ -775,7 +775,7 @@ bool CheckSymbols::visit(FunctionDefinitionAST *ast)
|
||||
declId = q->unqualified_name;
|
||||
|
||||
if (fun->isVirtual()) {
|
||||
addUse(declId, Use::VirtualMethod);
|
||||
addUse(declId, SemanticInfo::VirtualMethodUse);
|
||||
} else if (maybeVirtualMethod(fun->name())) {
|
||||
addVirtualMethod(_context.lookup(fun->name(), fun->enclosingScope()), declId, fun->argumentCount());
|
||||
}
|
||||
@@ -798,7 +798,7 @@ bool CheckSymbols::visit(FunctionDefinitionAST *ast)
|
||||
return false;
|
||||
}
|
||||
|
||||
void CheckSymbols::addUse(NameAST *ast, Use::Kind kind)
|
||||
void CheckSymbols::addUse(NameAST *ast, UseKind kind)
|
||||
{
|
||||
if (! ast)
|
||||
return;
|
||||
@@ -822,7 +822,7 @@ void CheckSymbols::addUse(NameAST *ast, Use::Kind kind)
|
||||
addUse(startToken, kind);
|
||||
}
|
||||
|
||||
void CheckSymbols::addUse(unsigned tokenIndex, Use::Kind kind)
|
||||
void CheckSymbols::addUse(unsigned tokenIndex, UseKind kind)
|
||||
{
|
||||
if (! tokenIndex)
|
||||
return;
|
||||
@@ -871,7 +871,7 @@ void CheckSymbols::addType(ClassOrNamespace *b, NameAST *ast)
|
||||
unsigned line, column;
|
||||
getTokenStartPosition(startToken, &line, &column);
|
||||
const unsigned length = tok.length();
|
||||
const Use use(line, column, length, Use::Type);
|
||||
const Use use(line, column, length, SemanticInfo::TypeUse);
|
||||
addUse(use);
|
||||
//qDebug() << "added use" << oo(ast->name) << line << column << length;
|
||||
}
|
||||
@@ -913,10 +913,10 @@ void CheckSymbols::addTypeOrStatic(const QList<LookupItem> &candidates, NameAST
|
||||
getTokenStartPosition(startToken, &line, &column);
|
||||
const unsigned length = tok.length();
|
||||
|
||||
Use::Kind kind = Use::Type;
|
||||
UseKind kind = SemanticInfo::TypeUse;
|
||||
|
||||
if (c->enclosingEnum() != 0)
|
||||
kind = Use::Static;
|
||||
kind = SemanticInfo::StaticUse;
|
||||
|
||||
const Use use(line, column, length, kind);
|
||||
addUse(use);
|
||||
@@ -951,7 +951,7 @@ void CheckSymbols::addClassMember(const QList<LookupItem> &candidates, NameAST *
|
||||
getTokenStartPosition(startToken, &line, &column);
|
||||
const unsigned length = tok.length();
|
||||
|
||||
const Use use(line, column, length, Use::Field);
|
||||
const Use use(line, column, length, SemanticInfo::FieldUse);
|
||||
addUse(use);
|
||||
break;
|
||||
}
|
||||
@@ -976,7 +976,7 @@ void CheckSymbols::addStatic(const QList<LookupItem> &candidates, NameAST *ast)
|
||||
getTokenStartPosition(startToken, &line, &column);
|
||||
const unsigned length = tok.length();
|
||||
|
||||
const Use use(line, column, length, Use::Static);
|
||||
const Use use(line, column, length, SemanticInfo::StaticUse);
|
||||
addUse(use);
|
||||
//qDebug() << "added use" << oo(ast->name) << line << column << length;
|
||||
break;
|
||||
@@ -1015,7 +1015,7 @@ void CheckSymbols::addVirtualMethod(const QList<LookupItem> &candidates, NameAST
|
||||
getTokenStartPosition(startToken, &line, &column);
|
||||
const unsigned length = tok.length();
|
||||
|
||||
const Use use(line, column, length, Use::VirtualMethod);
|
||||
const Use use(line, column, length, SemanticInfo::VirtualMethodUse);
|
||||
addUse(use);
|
||||
break;
|
||||
}
|
||||
|
@@ -55,6 +55,7 @@ public:
|
||||
virtual ~CheckSymbols();
|
||||
|
||||
typedef CppEditor::Internal::SemanticInfo::Use Use;
|
||||
typedef CppEditor::Internal::SemanticInfo::UseKind UseKind;
|
||||
|
||||
virtual void run();
|
||||
|
||||
@@ -110,8 +111,8 @@ protected:
|
||||
void checkNamespace(NameAST *name);
|
||||
|
||||
void addUse(const Use &use);
|
||||
void addUse(unsigned tokenIndex, Use::Kind kind);
|
||||
void addUse(NameAST *name, Use::Kind kind);
|
||||
void addUse(unsigned tokenIndex, UseKind kind);
|
||||
void addUse(NameAST *name, UseKind kind);
|
||||
|
||||
void addType(ClassOrNamespace *b, NameAST *ast);
|
||||
|
||||
|
@@ -87,6 +87,7 @@
|
||||
#include <texteditor/tabsettings.h>
|
||||
#include <texteditor/texteditorconstants.h>
|
||||
#include <texteditor/refactoroverlay.h>
|
||||
#include <texteditor/semantichighlighter.h>
|
||||
#include <texteditor/codeassist/basicproposalitemlistmodel.h>
|
||||
#include <texteditor/codeassist/basicproposalitem.h>
|
||||
#include <texteditor/codeassist/genericproposal.h>
|
||||
@@ -461,7 +462,6 @@ CPPEditorWidget::CPPEditorWidget(QWidget *parent)
|
||||
}
|
||||
|
||||
m_highlightRevision = 0;
|
||||
m_nextHighlightBlockNumber = 0;
|
||||
connect(&m_highlightWatcher, SIGNAL(resultsReadyAt(int,int)), SLOT(highlightSymbolUsages(int,int)));
|
||||
connect(&m_highlightWatcher, SIGNAL(finished()), SLOT(finishHighlightSymbolUsages()));
|
||||
|
||||
@@ -1010,68 +1010,11 @@ void CPPEditorWidget::highlightSymbolUsages(int from, int to)
|
||||
else if (m_highlighter.isCanceled())
|
||||
return; // aborted
|
||||
|
||||
CppHighlighter *highlighter = qobject_cast<CppHighlighter*>(baseTextDocument()->syntaxHighlighter());
|
||||
Q_ASSERT(highlighter);
|
||||
QTextDocument *doc = document();
|
||||
TextEditor::SyntaxHighlighter *highlighter = baseTextDocument()->syntaxHighlighter();
|
||||
QTC_ASSERT(highlighter, return);
|
||||
|
||||
if (m_nextHighlightBlockNumber >= doc->blockCount())
|
||||
return;
|
||||
|
||||
QMap<int, QVector<SemanticInfo::Use> > chunks = CheckSymbols::chunks(m_highlighter, from, to);
|
||||
if (chunks.isEmpty())
|
||||
return;
|
||||
|
||||
QTextBlock b = doc->findBlockByNumber(m_nextHighlightBlockNumber);
|
||||
|
||||
QMapIterator<int, QVector<SemanticInfo::Use> > it(chunks);
|
||||
while (b.isValid() && it.hasNext()) {
|
||||
it.next();
|
||||
const int blockNumber = it.key();
|
||||
Q_ASSERT(blockNumber < doc->blockCount());
|
||||
|
||||
while (m_nextHighlightBlockNumber < blockNumber) {
|
||||
highlighter->setExtraAdditionalFormats(b, QList<QTextLayout::FormatRange>());
|
||||
b = b.next();
|
||||
++m_nextHighlightBlockNumber;
|
||||
}
|
||||
|
||||
QList<QTextLayout::FormatRange> formats;
|
||||
foreach (const SemanticInfo::Use &use, it.value()) {
|
||||
QTextLayout::FormatRange formatRange;
|
||||
|
||||
switch (use.kind) {
|
||||
case SemanticInfo::Use::Type:
|
||||
formatRange.format = m_typeFormat;
|
||||
break;
|
||||
|
||||
case SemanticInfo::Use::Field:
|
||||
formatRange.format = m_fieldFormat;
|
||||
break;
|
||||
|
||||
case SemanticInfo::Use::Local:
|
||||
formatRange.format = m_localFormat;
|
||||
break;
|
||||
|
||||
case SemanticInfo::Use::Static:
|
||||
formatRange.format = m_staticFormat;
|
||||
break;
|
||||
|
||||
case SemanticInfo::Use::VirtualMethod:
|
||||
formatRange.format = m_virtualMethodFormat;
|
||||
break;
|
||||
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
|
||||
formatRange.start = use.column - 1;
|
||||
formatRange.length = use.length;
|
||||
formats.append(formatRange);
|
||||
}
|
||||
highlighter->setExtraAdditionalFormats(b, formats);
|
||||
b = b.next();
|
||||
++m_nextHighlightBlockNumber;
|
||||
}
|
||||
TextEditor::SemanticHighlighter::incrementalApplyExtraAdditionalFormats(
|
||||
highlighter, m_highlighter, from, to, m_semanticHighlightFormatMap);
|
||||
}
|
||||
|
||||
void CPPEditorWidget::finishHighlightSymbolUsages()
|
||||
@@ -1082,20 +1025,11 @@ void CPPEditorWidget::finishHighlightSymbolUsages()
|
||||
else if (m_highlighter.isCanceled())
|
||||
return; // aborted
|
||||
|
||||
CppHighlighter *highlighter = qobject_cast<CppHighlighter*>(baseTextDocument()->syntaxHighlighter());
|
||||
Q_ASSERT(highlighter);
|
||||
QTextDocument *doc = document();
|
||||
TextEditor::SyntaxHighlighter *highlighter = baseTextDocument()->syntaxHighlighter();
|
||||
QTC_ASSERT(highlighter, return);
|
||||
|
||||
if (m_nextHighlightBlockNumber >= doc->blockCount())
|
||||
return;
|
||||
|
||||
QTextBlock b = doc->findBlockByNumber(m_nextHighlightBlockNumber);
|
||||
|
||||
while (b.isValid()) {
|
||||
highlighter->setExtraAdditionalFormats(b, QList<QTextLayout::FormatRange>());
|
||||
b = b.next();
|
||||
++m_nextHighlightBlockNumber;
|
||||
}
|
||||
TextEditor::SemanticHighlighter::clearExtraAdditionalFormatsUntilEnd(
|
||||
highlighter, m_highlighter);
|
||||
}
|
||||
|
||||
|
||||
@@ -1777,11 +1711,16 @@ void CPPEditorWidget::setFontSettings(const TextEditor::FontSettings &fs)
|
||||
m_occurrencesUnusedFormat.clearForeground();
|
||||
m_occurrencesUnusedFormat.setToolTip(tr("Unused variable"));
|
||||
m_occurrenceRenameFormat = fs.toTextCharFormat(QLatin1String(TextEditor::Constants::C_OCCURRENCES_RENAME));
|
||||
m_typeFormat = fs.toTextCharFormat(QLatin1String(TextEditor::Constants::C_TYPE));
|
||||
m_localFormat = fs.toTextCharFormat(QLatin1String(TextEditor::Constants::C_LOCAL));
|
||||
m_fieldFormat = fs.toTextCharFormat(QLatin1String(TextEditor::Constants::C_FIELD));
|
||||
m_staticFormat = fs.toTextCharFormat(QLatin1String(TextEditor::Constants::C_STATIC));
|
||||
m_virtualMethodFormat = fs.toTextCharFormat(QLatin1String(TextEditor::Constants::C_VIRTUAL_METHOD));
|
||||
m_semanticHighlightFormatMap[SemanticInfo::TypeUse] =
|
||||
fs.toTextCharFormat(QLatin1String(TextEditor::Constants::C_TYPE));
|
||||
m_semanticHighlightFormatMap[SemanticInfo::LocalUse] =
|
||||
fs.toTextCharFormat(QLatin1String(TextEditor::Constants::C_LOCAL));
|
||||
m_semanticHighlightFormatMap[SemanticInfo::FieldUse] =
|
||||
fs.toTextCharFormat(QLatin1String(TextEditor::Constants::C_FIELD));
|
||||
m_semanticHighlightFormatMap[SemanticInfo::StaticUse] =
|
||||
fs.toTextCharFormat(QLatin1String(TextEditor::Constants::C_STATIC));
|
||||
m_semanticHighlightFormatMap[SemanticInfo::VirtualMethodUse] =
|
||||
fs.toTextCharFormat(QLatin1String(TextEditor::Constants::C_VIRTUAL_METHOD));
|
||||
m_keywordFormat = fs.toTextCharFormat(QLatin1String(TextEditor::Constants::C_KEYWORD));
|
||||
|
||||
// only set the background, we do not want to modify foreground properties set by the syntax highlighter or the link
|
||||
@@ -1921,7 +1860,6 @@ void CPPEditorWidget::updateSemanticInfo(const SemanticInfo &semanticInfo)
|
||||
CheckSymbols::Future f = CheckSymbols::go(semanticInfo.doc, context);
|
||||
m_highlighter = f;
|
||||
m_highlightRevision = semanticInfo.revision;
|
||||
m_nextHighlightBlockNumber = 0;
|
||||
m_highlightWatcher.setFuture(m_highlighter);
|
||||
}
|
||||
}
|
||||
|
@@ -297,12 +297,8 @@ private:
|
||||
QTextCharFormat m_occurrencesFormat;
|
||||
QTextCharFormat m_occurrencesUnusedFormat;
|
||||
QTextCharFormat m_occurrenceRenameFormat;
|
||||
QTextCharFormat m_typeFormat;
|
||||
QTextCharFormat m_localFormat;
|
||||
QTextCharFormat m_fieldFormat;
|
||||
QTextCharFormat m_staticFormat;
|
||||
QHash<int, QTextCharFormat> m_semanticHighlightFormatMap;
|
||||
QTextCharFormat m_keywordFormat;
|
||||
QTextCharFormat m_virtualMethodFormat;
|
||||
|
||||
QList<QTextEdit::ExtraSelection> m_renameSelections;
|
||||
int m_currentRenameSelection;
|
||||
@@ -320,7 +316,6 @@ private:
|
||||
QFuture<SemanticInfo::Use> m_highlighter;
|
||||
QFutureWatcher<SemanticInfo::Use> m_highlightWatcher;
|
||||
unsigned m_highlightRevision; // the editor revision that requested the highlight
|
||||
int m_nextHighlightBlockNumber;
|
||||
|
||||
QFuture<QList<int> > m_references;
|
||||
QFutureWatcher<QList<int> > m_referencesWatcher;
|
||||
|
@@ -99,7 +99,7 @@ protected:
|
||||
const Identifier *id = member->identifier();
|
||||
unsigned line, column;
|
||||
getTokenStartPosition(member->sourceLocation(), &line, &column);
|
||||
localUses[member].append(SemanticInfo::Use(line, column, id->size(), SemanticInfo::Use::Local));
|
||||
localUses[member].append(SemanticInfo::Use(line, column, id->size(), SemanticInfo::LocalUse));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -117,7 +117,7 @@ protected:
|
||||
else if (!member->isGenerated() && (member->sourceLocation() < ast->firstToken() || member->enclosingScope()->isFunction())) {
|
||||
unsigned line, column;
|
||||
getTokenStartPosition(simpleName->identifier_token, &line, &column);
|
||||
localUses[member].append(SemanticInfo::Use(line, column, id->size(), SemanticInfo::Use::Local));
|
||||
localUses[member].append(SemanticInfo::Use(line, column, id->size(), SemanticInfo::LocalUse));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@@ -35,6 +35,7 @@
|
||||
|
||||
#include <cplusplus/CppDocument.h>
|
||||
#include <cplusplus/LookupContext.h>
|
||||
#include <texteditor/semantichighlighter.h>
|
||||
#include <QtCore/QHash>
|
||||
|
||||
namespace CppEditor {
|
||||
@@ -45,22 +46,13 @@ class CPPEditorWidget;
|
||||
class SemanticInfo
|
||||
{
|
||||
public:
|
||||
struct Use {
|
||||
unsigned line;
|
||||
unsigned column;
|
||||
unsigned length;
|
||||
unsigned kind;
|
||||
|
||||
enum Kind {
|
||||
Type = 0,
|
||||
Local,
|
||||
Field,
|
||||
Static,
|
||||
VirtualMethod
|
||||
};
|
||||
|
||||
Use(unsigned line = 0, unsigned column = 0, unsigned length = 0, unsigned kind = Type)
|
||||
: line(line), column(column), length(length), kind(kind) {}
|
||||
typedef TextEditor::SemanticHighlighter::Result Use;
|
||||
enum UseKind {
|
||||
TypeUse = 0,
|
||||
LocalUse,
|
||||
FieldUse,
|
||||
StaticUse,
|
||||
VirtualMethodUse
|
||||
};
|
||||
|
||||
typedef QHash<CPlusPlus::Symbol *, QList<Use> > LocalUseMap;
|
||||
|
@@ -41,6 +41,7 @@
|
||||
#include "qmljsautocompleter.h"
|
||||
#include "qmljscompletionassist.h"
|
||||
#include "qmljsquickfixassist.h"
|
||||
#include "qmljssemantichighlighter.h"
|
||||
|
||||
#include <qmljs/qmljsbind.h>
|
||||
#include <qmljs/qmljsevaluate.h>
|
||||
@@ -658,7 +659,8 @@ QmlJSTextEditorWidget::QmlJSTextEditorWidget(QWidget *parent) :
|
||||
m_modelManager(0),
|
||||
m_contextPane(0),
|
||||
m_updateSelectedElements(false),
|
||||
m_findReferences(new FindReferences(this))
|
||||
m_findReferences(new FindReferences(this)),
|
||||
m_semanticHighlighter(new SemanticHighlighter(this))
|
||||
{
|
||||
qRegisterMetaType<QmlJSEditor::SemanticInfo>("QmlJSEditor::SemanticInfo");
|
||||
|
||||
@@ -1218,6 +1220,8 @@ void QmlJSTextEditorWidget::setFontSettings(const TextEditor::FontSettings &fs)
|
||||
// only set the background, we do not want to modify foreground properties set by the syntax highlighter or the link
|
||||
m_occurrencesFormat.clearForeground();
|
||||
m_occurrenceRenameFormat.clearForeground();
|
||||
|
||||
m_semanticHighlighter->updateFontSettings(fs);
|
||||
}
|
||||
|
||||
|
||||
@@ -1544,9 +1548,6 @@ void QmlJSTextEditorWidget::updateSemanticInfo(const SemanticInfo &semanticInfo)
|
||||
FindIdDeclarations updateIds;
|
||||
m_semanticInfo.idLocations = updateIds(doc);
|
||||
|
||||
FindDeclarations findDeclarations;
|
||||
m_semanticInfo.declarations = findDeclarations(doc->ast());
|
||||
|
||||
if (m_contextPane) {
|
||||
Node *newNode = m_semanticInfo.declaringMemberNoProperties(position());
|
||||
if (newNode) {
|
||||
@@ -1563,6 +1564,10 @@ void QmlJSTextEditorWidget::updateSemanticInfo(const SemanticInfo &semanticInfo)
|
||||
appendExtraSelectionsForMessages(&selections, doc->diagnosticMessages(), document());
|
||||
appendExtraSelectionsForMessages(&selections, m_semanticInfo.semanticMessages, document());
|
||||
setExtraSelections(CodeWarningsSelection, selections);
|
||||
|
||||
Core::EditorManager *editorManager = Core::EditorManager::instance();
|
||||
if (editorManager->currentEditor() == editor())
|
||||
m_semanticHighlighter->rerun(m_semanticInfo.scopeChain());
|
||||
}
|
||||
|
||||
void QmlJSTextEditorWidget::onRefactorMarkerClicked(const TextEditor::RefactorMarker &marker)
|
||||
|
@@ -70,6 +70,7 @@ namespace Internal {
|
||||
class QmlOutlineModel;
|
||||
class SemanticInfoUpdater;
|
||||
struct SemanticInfoUpdaterSource;
|
||||
class SemanticHighlighter;
|
||||
} // namespace Internal
|
||||
|
||||
struct QMLJSEDITOR_EXPORT Declaration
|
||||
@@ -132,7 +133,6 @@ public: // attributes
|
||||
QmlJS::ContextPtr context;
|
||||
QList<Range> ranges;
|
||||
QHash<QString, QList<QmlJS::AST::SourceLocation> > idLocations;
|
||||
QList<Declaration> declarations;
|
||||
|
||||
// these are in addition to the parser messages in the document
|
||||
QList<QmlJS::DiagnosticMessage> semanticMessages;
|
||||
@@ -251,6 +251,9 @@ private:
|
||||
bool m_updateSelectedElements;
|
||||
|
||||
FindReferences *m_findReferences;
|
||||
Internal::SemanticHighlighter *m_semanticHighlighter;
|
||||
|
||||
friend class Internal::SemanticHighlighter;
|
||||
};
|
||||
|
||||
} // namespace QmlJSEditor
|
||||
|
@@ -36,7 +36,8 @@ HEADERS += \
|
||||
qmljsquickfixassist.h \
|
||||
qmljscompletionassist.h \
|
||||
qmljsquickfix.h \
|
||||
qmljssemanticinfoupdater.h
|
||||
qmljssemanticinfoupdater.h \
|
||||
qmljssemantichighlighter.h
|
||||
|
||||
SOURCES += \
|
||||
qmljseditor.cpp \
|
||||
@@ -66,7 +67,8 @@ SOURCES += \
|
||||
qmljsquickfixassist.cpp \
|
||||
qmljscompletionassist.cpp \
|
||||
qmljsquickfix.cpp \
|
||||
qmljssemanticinfoupdater.cpp
|
||||
qmljssemanticinfoupdater.cpp \
|
||||
qmljssemantichighlighter.cpp
|
||||
|
||||
RESOURCES += qmljseditor.qrc
|
||||
OTHER_FILES += QmlJSEditor.mimetypes.xml
|
||||
@@ -74,7 +76,3 @@ OTHER_FILES += QmlJSEditor.mimetypes.xml
|
||||
FORMS += \
|
||||
quicktoolbarsettingspage.ui \
|
||||
qmljscomponentnamedialog.ui
|
||||
|
||||
|
||||
|
||||
|
||||
|
406
src/plugins/qmljseditor/qmljssemantichighlighter.cpp
Normal file
406
src/plugins/qmljseditor/qmljssemantichighlighter.cpp
Normal file
@@ -0,0 +1,406 @@
|
||||
/**************************************************************************
|
||||
**
|
||||
** This file is part of Qt Creator
|
||||
**
|
||||
** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
|
||||
**
|
||||
** Contact: Nokia Corporation (info@qt.nokia.com)
|
||||
**
|
||||
**
|
||||
** GNU Lesser General Public License Usage
|
||||
**
|
||||
** This file may be used under the terms of the GNU Lesser General Public
|
||||
** License version 2.1 as published by the Free Software Foundation and
|
||||
** appearing in the file LICENSE.LGPL included in the packaging of this file.
|
||||
** Please review the following information to ensure the GNU Lesser General
|
||||
** Public License version 2.1 requirements will be met:
|
||||
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
|
||||
**
|
||||
** In addition, as a special exception, Nokia gives you certain additional
|
||||
** rights. These rights are described in the Nokia Qt LGPL Exception
|
||||
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
|
||||
**
|
||||
** Other Usage
|
||||
**
|
||||
** Alternatively, this file may be used in accordance with the terms and
|
||||
** conditions contained in a signed written agreement between you and Nokia.
|
||||
**
|
||||
** If you have questions regarding the use of this file, please contact
|
||||
** Nokia at info@qt.nokia.com.
|
||||
**
|
||||
**************************************************************************/
|
||||
|
||||
#include "qmljssemantichighlighter.h"
|
||||
|
||||
#include "qmljseditor.h"
|
||||
|
||||
#include <qmljs/qmljsdocument.h>
|
||||
#include <qmljs/qmljsscopechain.h>
|
||||
#include <qmljs/qmljsscopebuilder.h>
|
||||
#include <qmljs/qmljsevaluate.h>
|
||||
#include <qmljs/qmljscontext.h>
|
||||
#include <qmljs/qmljsbind.h>
|
||||
#include <qmljs/parser/qmljsast_p.h>
|
||||
#include <qmljs/parser/qmljsastvisitor_p.h>
|
||||
#include <texteditor/syntaxhighlighter.h>
|
||||
#include <texteditor/basetextdocument.h>
|
||||
#include <texteditor/texteditorconstants.h>
|
||||
#include <texteditor/fontsettings.h>
|
||||
#include <utils/qtcassert.h>
|
||||
|
||||
#include <QtCore/QThreadPool>
|
||||
#include <QtCore/QFutureInterface>
|
||||
#include <QtCore/QRunnable>
|
||||
|
||||
using namespace QmlJSEditor;
|
||||
using namespace QmlJSEditor::Internal;
|
||||
using namespace QmlJS;
|
||||
using namespace QmlJS::AST;
|
||||
|
||||
namespace {
|
||||
|
||||
template <typename T>
|
||||
class PriorityTask :
|
||||
public QFutureInterface<T>,
|
||||
public QRunnable
|
||||
{
|
||||
public:
|
||||
typedef QFuture<T> Future;
|
||||
|
||||
Future start(QThread::Priority priority)
|
||||
{
|
||||
this->setRunnable(this);
|
||||
this->reportStarted();
|
||||
Future future = this->future();
|
||||
QThreadPool::globalInstance()->start(this, priority);
|
||||
return future;
|
||||
}
|
||||
};
|
||||
|
||||
static bool isIdScope(const ObjectValue *scope, const QList<const QmlComponentChain *> &chain)
|
||||
{
|
||||
foreach (const QmlComponentChain *c, chain) {
|
||||
if (c->idScope() == scope)
|
||||
return true;
|
||||
if (isIdScope(scope, c->instantiatingComponents()))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
class CollectStateNames : protected Visitor
|
||||
{
|
||||
QStringList m_stateNames;
|
||||
bool m_inStateType;
|
||||
ScopeChain m_scopeChain;
|
||||
const QmlObjectValue *m_statePrototype;
|
||||
|
||||
public:
|
||||
CollectStateNames(const ScopeChain &scopeChain)
|
||||
: m_scopeChain(scopeChain)
|
||||
{
|
||||
m_statePrototype = scopeChain.context()->valueOwner()->cppQmlTypes().typeByCppName(QLatin1String("QDeclarativeState"));
|
||||
}
|
||||
|
||||
QStringList operator()(Node *ast)
|
||||
{
|
||||
m_stateNames.clear();
|
||||
if (!m_statePrototype)
|
||||
return m_stateNames;
|
||||
m_inStateType = false;
|
||||
accept(ast);
|
||||
return m_stateNames;
|
||||
}
|
||||
|
||||
protected:
|
||||
void accept(Node *ast)
|
||||
{
|
||||
if (ast)
|
||||
ast->accept(this);
|
||||
}
|
||||
|
||||
bool preVisit(Node *ast)
|
||||
{
|
||||
return ast->uiObjectMemberCast()
|
||||
|| cast<UiProgram *>(ast)
|
||||
|| cast<UiObjectInitializer *>(ast)
|
||||
|| cast<UiObjectMemberList *>(ast)
|
||||
|| cast<UiArrayMemberList *>(ast);
|
||||
}
|
||||
|
||||
bool hasStatePrototype(Node *ast)
|
||||
{
|
||||
Bind *bind = m_scopeChain.document()->bind();
|
||||
const ObjectValue *v = bind->findQmlObject(ast);
|
||||
if (!v)
|
||||
return false;
|
||||
PrototypeIterator it(v, m_scopeChain.context());
|
||||
while (it.hasNext()) {
|
||||
const ObjectValue *proto = it.next();
|
||||
const QmlObjectValue *qmlProto = dynamic_cast<const QmlObjectValue *>(proto);
|
||||
if (!qmlProto)
|
||||
continue;
|
||||
if (qmlProto->metaObject() == m_statePrototype->metaObject())
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool visit(UiObjectDefinition *ast)
|
||||
{
|
||||
const bool old = m_inStateType;
|
||||
m_inStateType = hasStatePrototype(ast);
|
||||
accept(ast->initializer);
|
||||
m_inStateType = old;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool visit(UiObjectBinding *ast)
|
||||
{
|
||||
const bool old = m_inStateType;
|
||||
m_inStateType = hasStatePrototype(ast);
|
||||
accept(ast->initializer);
|
||||
m_inStateType = old;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool visit(UiScriptBinding *ast)
|
||||
{
|
||||
if (!m_inStateType)
|
||||
return false;
|
||||
if (!ast->qualifiedId || ! ast->qualifiedId->name || ast->qualifiedId->next)
|
||||
return false;
|
||||
if (ast->qualifiedId->name->asString() != QLatin1String("name"))
|
||||
return false;
|
||||
|
||||
ExpressionStatement *expStmt = cast<ExpressionStatement *>(ast->statement);
|
||||
if (!expStmt)
|
||||
return false;
|
||||
StringLiteral *strLit = cast<StringLiteral *>(expStmt->expression);
|
||||
if (!strLit || !strLit->value)
|
||||
return false;
|
||||
|
||||
m_stateNames += strLit->value->asString();
|
||||
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
class CollectionTask :
|
||||
public PriorityTask<SemanticHighlighter::Use>,
|
||||
protected Visitor
|
||||
{
|
||||
public:
|
||||
CollectionTask(const ScopeChain &scopeChain)
|
||||
: m_scopeChain(scopeChain)
|
||||
, m_scopeBuilder(&m_scopeChain)
|
||||
{}
|
||||
|
||||
protected:
|
||||
void accept(Node *ast)
|
||||
{
|
||||
if (ast)
|
||||
ast->accept(this);
|
||||
}
|
||||
|
||||
void scopedAccept(Node *ast, Node *child)
|
||||
{
|
||||
m_scopeBuilder.push(ast);
|
||||
accept(child);
|
||||
m_scopeBuilder.pop();
|
||||
}
|
||||
|
||||
void processName(NameId *name, SourceLocation location)
|
||||
{
|
||||
if (!name)
|
||||
return;
|
||||
|
||||
const QString nameStr = name->asString();
|
||||
const ObjectValue *scope = 0;
|
||||
const Value *value = m_scopeChain.lookup(nameStr, &scope);
|
||||
if (!value || !scope)
|
||||
return;
|
||||
|
||||
SemanticHighlighter::Use use = SemanticHighlighter::makeUse(location);
|
||||
if (QSharedPointer<const QmlComponentChain> chain = m_scopeChain.qmlComponentChain()) {
|
||||
if (scope == chain->idScope()) {
|
||||
use.kind = SemanticHighlighter::LocalIdType;
|
||||
} else if (isIdScope(scope, chain->instantiatingComponents())) {
|
||||
use.kind = SemanticHighlighter::ExternalIdType;
|
||||
} else if (scope == chain->rootObjectScope()) {
|
||||
use.kind = SemanticHighlighter::RootObjectPropertyType;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_scopeChain.qmlTypes() == scope) {
|
||||
use.kind = SemanticHighlighter::QmlTypeType;
|
||||
} else if (m_scopeChain.qmlScopeObjects().contains(scope)) {
|
||||
use.kind = SemanticHighlighter::ScopeObjectPropertyType;
|
||||
} else if (m_scopeChain.jsScopes().contains(scope)) {
|
||||
use.kind = SemanticHighlighter::JsScopeType;
|
||||
} else if (m_scopeChain.jsImports() == scope) {
|
||||
use.kind = SemanticHighlighter::JsImportType;
|
||||
} else if (m_scopeChain.globalScope() == scope) {
|
||||
use.kind = SemanticHighlighter::JsGlobalType;
|
||||
}
|
||||
|
||||
// eliminated other possibilities, should potentially be a real check if this yields false-positives
|
||||
if (use.kind == SemanticHighlighter::UnknownType) {
|
||||
use.kind = SemanticHighlighter::ExternalObjectPropertyType;
|
||||
}
|
||||
|
||||
reportResult(use);
|
||||
}
|
||||
|
||||
bool visit(UiObjectDefinition *ast)
|
||||
{
|
||||
scopedAccept(ast, ast->initializer);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool visit(UiObjectBinding *ast)
|
||||
{
|
||||
scopedAccept(ast, ast->initializer);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool visit(UiScriptBinding *ast)
|
||||
{
|
||||
scopedAccept(ast, ast->statement);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool visit(UiPublicMember *ast)
|
||||
{
|
||||
scopedAccept(ast, ast->statement);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool visit(FunctionExpression *ast)
|
||||
{
|
||||
processName(ast->name, ast->identifierToken);
|
||||
scopedAccept(ast, ast->body);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool visit(FunctionDeclaration *ast)
|
||||
{
|
||||
return visit(static_cast<FunctionExpression *>(ast));
|
||||
}
|
||||
|
||||
bool visit(VariableDeclaration *ast)
|
||||
{
|
||||
processName(ast->name, ast->identifierToken);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool visit(IdentifierExpression *ast)
|
||||
{
|
||||
processName(ast->name, ast->identifierToken);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool visit(StringLiteral *ast)
|
||||
{
|
||||
if (!ast->value)
|
||||
return false;
|
||||
|
||||
const QString value = ast->value->asString();
|
||||
if (m_stateNames.contains(value)) {
|
||||
SemanticHighlighter::Use use = SemanticHighlighter::makeUse(
|
||||
ast->literalToken, SemanticHighlighter::LocalStateNameType);
|
||||
reportResult(use);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
void run()
|
||||
{
|
||||
Node *root = m_scopeChain.document()->ast();
|
||||
m_stateNames = CollectStateNames(m_scopeChain)(root);
|
||||
accept(root);
|
||||
reportFinished();
|
||||
}
|
||||
|
||||
ScopeChain m_scopeChain;
|
||||
ScopeBuilder m_scopeBuilder;
|
||||
QStringList m_stateNames;
|
||||
};
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
SemanticHighlighter::SemanticHighlighter(QmlJSTextEditorWidget *editor)
|
||||
: QObject(editor)
|
||||
, m_editor(editor)
|
||||
, m_startRevision(0)
|
||||
{
|
||||
connect(&m_watcher, SIGNAL(resultsReadyAt(int,int)),
|
||||
this, SLOT(applyResults(int,int)));
|
||||
connect(&m_watcher, SIGNAL(finished()),
|
||||
this, SLOT(finished()));
|
||||
}
|
||||
|
||||
void SemanticHighlighter::rerun(const ScopeChain &scopeChain)
|
||||
{
|
||||
m_watcher.cancel();
|
||||
|
||||
// this does not simply use QtConcurrentRun because we want a low-priority future
|
||||
// the thread pool deletes the task when it is done
|
||||
CollectionTask::Future f = (new CollectionTask(scopeChain))->start(QThread::LowestPriority);
|
||||
m_startRevision = m_editor->editorRevision();
|
||||
m_watcher.setFuture(f);
|
||||
}
|
||||
|
||||
void SemanticHighlighter::applyResults(int from, int to)
|
||||
{
|
||||
if (m_watcher.isCanceled())
|
||||
return;
|
||||
if (m_startRevision != m_editor->editorRevision())
|
||||
return;
|
||||
|
||||
TextEditor::BaseTextDocument *baseTextDocument = m_editor->baseTextDocument();
|
||||
QTC_ASSERT(baseTextDocument, return);
|
||||
TextEditor::SyntaxHighlighter *highlighter = qobject_cast<TextEditor::SyntaxHighlighter *>(baseTextDocument->syntaxHighlighter());
|
||||
QTC_ASSERT(highlighter, return);
|
||||
|
||||
TextEditor::SemanticHighlighter::incrementalApplyExtraAdditionalFormats(
|
||||
highlighter, m_watcher.future(), from, to, m_formats);
|
||||
}
|
||||
|
||||
void SemanticHighlighter::finished()
|
||||
{
|
||||
if (m_watcher.isCanceled())
|
||||
return;
|
||||
if (m_startRevision != m_editor->editorRevision())
|
||||
return;
|
||||
|
||||
TextEditor::BaseTextDocument *baseTextDocument = m_editor->baseTextDocument();
|
||||
QTC_ASSERT(baseTextDocument, return);
|
||||
TextEditor::SyntaxHighlighter *highlighter = qobject_cast<TextEditor::SyntaxHighlighter *>(baseTextDocument->syntaxHighlighter());
|
||||
QTC_ASSERT(highlighter, return);
|
||||
|
||||
TextEditor::SemanticHighlighter::clearExtraAdditionalFormatsUntilEnd(
|
||||
highlighter, m_watcher.future());
|
||||
}
|
||||
|
||||
SemanticHighlighter::Use SemanticHighlighter::makeUse(const SourceLocation &location, SemanticHighlighter::UseType type)
|
||||
{
|
||||
return Use(location.startLine, location.startColumn, location.length, type);
|
||||
}
|
||||
|
||||
void SemanticHighlighter::updateFontSettings(const TextEditor::FontSettings &fontSettings)
|
||||
{
|
||||
m_formats[LocalIdType] = fontSettings.toTextCharFormat(QLatin1String(TextEditor::Constants::C_QML_LOCAL_ID));
|
||||
m_formats[ExternalIdType] = fontSettings.toTextCharFormat(QLatin1String(TextEditor::Constants::C_QML_EXTERNAL_ID));
|
||||
m_formats[QmlTypeType] = fontSettings.toTextCharFormat(QLatin1String(TextEditor::Constants::C_QML_TYPE_ID));
|
||||
m_formats[RootObjectPropertyType] = fontSettings.toTextCharFormat(QLatin1String(TextEditor::Constants::C_QML_ROOT_OBJECT_PROPERTY));
|
||||
m_formats[ScopeObjectPropertyType] = fontSettings.toTextCharFormat(QLatin1String(TextEditor::Constants::C_QML_SCOPE_OBJECT_PROPERTY));
|
||||
m_formats[ExternalObjectPropertyType] = fontSettings.toTextCharFormat(QLatin1String(TextEditor::Constants::C_QML_EXTERNAL_OBJECT_PROPERTY));
|
||||
m_formats[JsScopeType] = fontSettings.toTextCharFormat(QLatin1String(TextEditor::Constants::C_JS_SCOPE_VAR));
|
||||
m_formats[JsImportType] = fontSettings.toTextCharFormat(QLatin1String(TextEditor::Constants::C_JS_IMPORT_VAR));
|
||||
m_formats[JsGlobalType] = fontSettings.toTextCharFormat(QLatin1String(TextEditor::Constants::C_JS_GLOBAL_VAR));
|
||||
m_formats[LocalStateNameType] = fontSettings.toTextCharFormat(QLatin1String(TextEditor::Constants::C_QML_STATE_NAME));
|
||||
}
|
||||
|
98
src/plugins/qmljseditor/qmljssemantichighlighter.h
Normal file
98
src/plugins/qmljseditor/qmljssemantichighlighter.h
Normal file
@@ -0,0 +1,98 @@
|
||||
/**************************************************************************
|
||||
**
|
||||
** This file is part of Qt Creator
|
||||
**
|
||||
** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
|
||||
**
|
||||
** Contact: Nokia Corporation (info@qt.nokia.com)
|
||||
**
|
||||
**
|
||||
** GNU Lesser General Public License Usage
|
||||
**
|
||||
** This file may be used under the terms of the GNU Lesser General Public
|
||||
** License version 2.1 as published by the Free Software Foundation and
|
||||
** appearing in the file LICENSE.LGPL included in the packaging of this file.
|
||||
** Please review the following information to ensure the GNU Lesser General
|
||||
** Public License version 2.1 requirements will be met:
|
||||
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
|
||||
**
|
||||
** In addition, as a special exception, Nokia gives you certain additional
|
||||
** rights. These rights are described in the Nokia Qt LGPL Exception
|
||||
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
|
||||
**
|
||||
** Other Usage
|
||||
**
|
||||
** Alternatively, this file may be used in accordance with the terms and
|
||||
** conditions contained in a signed written agreement between you and Nokia.
|
||||
**
|
||||
** If you have questions regarding the use of this file, please contact
|
||||
** Nokia at info@qt.nokia.com.
|
||||
**
|
||||
**************************************************************************/
|
||||
|
||||
#ifndef QMLJSSEMANTICHIGHLIGHTER_H
|
||||
#define QMLJSSEMANTICHIGHLIGHTER_H
|
||||
|
||||
#include <texteditor/semantichighlighter.h>
|
||||
#include <QtCore/QFutureWatcher>
|
||||
|
||||
namespace QmlJS {
|
||||
class ScopeChain;
|
||||
namespace AST {
|
||||
class SourceLocation;
|
||||
}
|
||||
}
|
||||
|
||||
namespace TextEditor {
|
||||
class FontSettings;
|
||||
}
|
||||
|
||||
namespace QmlJSEditor {
|
||||
|
||||
class QmlJSTextEditorWidget;
|
||||
|
||||
namespace Internal {
|
||||
|
||||
class SemanticHighlighter : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum UseType
|
||||
{
|
||||
UnknownType,
|
||||
LocalIdType, // ids in the same file
|
||||
ExternalIdType, // ids from instantiating files
|
||||
QmlTypeType, // qml types
|
||||
RootObjectPropertyType, // property in root object
|
||||
ScopeObjectPropertyType, // property in scope object
|
||||
ExternalObjectPropertyType, // property in root object of instantiating file
|
||||
JsScopeType, // var or function in local js scope
|
||||
JsImportType, // name of js import
|
||||
JsGlobalType, // in global scope
|
||||
LocalStateNameType // name of a state in the current file
|
||||
};
|
||||
|
||||
typedef TextEditor::SemanticHighlighter::Result Use;
|
||||
static Use makeUse(const QmlJS::AST::SourceLocation &location, UseType type = UnknownType);
|
||||
|
||||
SemanticHighlighter(QmlJSTextEditorWidget *editor);
|
||||
|
||||
void rerun(const QmlJS::ScopeChain &scopeChain);
|
||||
|
||||
void updateFontSettings(const TextEditor::FontSettings &fontSettings);
|
||||
|
||||
private slots:
|
||||
void applyResults(int from, int to);
|
||||
void finished();
|
||||
|
||||
private:
|
||||
QFutureWatcher<Use> m_watcher;
|
||||
QmlJSTextEditorWidget *m_editor;
|
||||
int m_startRevision;
|
||||
QHash<int, QTextCharFormat> m_formats;
|
||||
};
|
||||
|
||||
} // namespace Internal
|
||||
} // namespace QmlJSEditor
|
||||
|
||||
#endif // QMLJSSEMANTICHIGHLIGHTER_H
|
@@ -239,6 +239,13 @@ FormatDescription::FormatDescription(const QString &id, const QString &displayNa
|
||||
m_format.setForeground(color);
|
||||
}
|
||||
|
||||
FormatDescription::FormatDescription(const QString &id, const QString &displayName, const Format &format) :
|
||||
m_id(id),
|
||||
m_displayName(displayName),
|
||||
m_format(format)
|
||||
{
|
||||
}
|
||||
|
||||
QColor FormatDescription::foreground() const
|
||||
{
|
||||
if (m_id == QLatin1String(Constants::C_LINE_NUMBER)) {
|
||||
|
@@ -60,6 +60,8 @@ class TEXTEDITOR_EXPORT FormatDescription
|
||||
public:
|
||||
FormatDescription(const QString &id, const QString &displayName,
|
||||
const QColor &foreground = Qt::black);
|
||||
FormatDescription(const QString &id, const QString &displayName,
|
||||
const Format &format);
|
||||
|
||||
QString id() const
|
||||
{ return m_id; }
|
||||
|
138
src/plugins/texteditor/semantichighlighter.cpp
Normal file
138
src/plugins/texteditor/semantichighlighter.cpp
Normal file
@@ -0,0 +1,138 @@
|
||||
/**************************************************************************
|
||||
**
|
||||
** This file is part of Qt Creator
|
||||
**
|
||||
** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
|
||||
**
|
||||
** Contact: Nokia Corporation (info@qt.nokia.com)
|
||||
**
|
||||
**
|
||||
** GNU Lesser General Public License Usage
|
||||
**
|
||||
** This file may be used under the terms of the GNU Lesser General Public
|
||||
** License version 2.1 as published by the Free Software Foundation and
|
||||
** appearing in the file LICENSE.LGPL included in the packaging of this file.
|
||||
** Please review the following information to ensure the GNU Lesser General
|
||||
** Public License version 2.1 requirements will be met:
|
||||
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
|
||||
**
|
||||
** In addition, as a special exception, Nokia gives you certain additional
|
||||
** rights. These rights are described in the Nokia Qt LGPL Exception
|
||||
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
|
||||
**
|
||||
** Other Usage
|
||||
**
|
||||
** Alternatively, this file may be used in accordance with the terms and
|
||||
** conditions contained in a signed written agreement between you and Nokia.
|
||||
**
|
||||
** If you have questions regarding the use of this file, please contact
|
||||
** Nokia at info@qt.nokia.com.
|
||||
**
|
||||
**************************************************************************/
|
||||
|
||||
#include "semantichighlighter.h"
|
||||
|
||||
#include "syntaxhighlighter.h"
|
||||
|
||||
#include <utils/qtcassert.h>
|
||||
|
||||
#include <QtGui/QTextDocument>
|
||||
#include <QtGui/QTextBlock>
|
||||
|
||||
using namespace TextEditor;
|
||||
using namespace TextEditor::SemanticHighlighter;
|
||||
|
||||
void TextEditor::SemanticHighlighter::incrementalApplyExtraAdditionalFormats(
|
||||
SyntaxHighlighter *highlighter,
|
||||
const QFuture<Result> &future,
|
||||
int from, int to,
|
||||
const QHash<int, QTextCharFormat> &kindToFormat)
|
||||
{
|
||||
QMap<int, QVector<Result> > blockNumberToResults;
|
||||
for (int i = from; i < to; ++i) {
|
||||
const Result &result = future.resultAt(i);
|
||||
if (!result.line)
|
||||
continue;
|
||||
blockNumberToResults[result.line - 1].append(result);
|
||||
}
|
||||
if (blockNumberToResults.isEmpty())
|
||||
return;
|
||||
|
||||
const int firstResultBlockNumber = blockNumberToResults.constBegin().key();
|
||||
|
||||
// blocks between currentBlockNumber and the last block with results will
|
||||
// be cleaned of additional extra formats if they have no results
|
||||
int currentBlockNumber = 0;
|
||||
for (int i = from - 1; i >= 0; --i) {
|
||||
const Result &result = future.resultAt(i);
|
||||
if (!result.line)
|
||||
continue;
|
||||
const int blockNumber = result.line - 1;
|
||||
if (blockNumber < firstResultBlockNumber) {
|
||||
// stop! found where last format stopped
|
||||
currentBlockNumber = blockNumber + 1;
|
||||
break;
|
||||
} else {
|
||||
// add previous results for the same line to avoid undoing their formats
|
||||
blockNumberToResults[blockNumber].append(result);
|
||||
}
|
||||
}
|
||||
|
||||
QTextDocument *doc = highlighter->document();
|
||||
QTC_ASSERT(currentBlockNumber < doc->blockCount(), return);
|
||||
QTextBlock b = doc->findBlockByNumber(currentBlockNumber);
|
||||
|
||||
QMapIterator<int, QVector<Result> > it(blockNumberToResults);
|
||||
while (b.isValid() && it.hasNext()) {
|
||||
it.next();
|
||||
const int blockNumber = it.key();
|
||||
QTC_ASSERT(blockNumber < doc->blockCount(), return);
|
||||
|
||||
// clear formats of blocks until blockNumber
|
||||
while (currentBlockNumber < blockNumber) {
|
||||
highlighter->setExtraAdditionalFormats(b, QList<QTextLayout::FormatRange>());
|
||||
b = b.next();
|
||||
++currentBlockNumber;
|
||||
}
|
||||
|
||||
QList<QTextLayout::FormatRange> formats;
|
||||
foreach (const Result &result, it.value()) {
|
||||
QTextLayout::FormatRange formatRange;
|
||||
|
||||
formatRange.format = kindToFormat.value(result.kind);
|
||||
if (!formatRange.format.isValid())
|
||||
continue;
|
||||
|
||||
formatRange.start = result.column - 1;
|
||||
formatRange.length = result.length;
|
||||
formats.append(formatRange);
|
||||
}
|
||||
highlighter->setExtraAdditionalFormats(b, formats);
|
||||
b = b.next();
|
||||
++currentBlockNumber;
|
||||
}
|
||||
}
|
||||
|
||||
void TextEditor::SemanticHighlighter::clearExtraAdditionalFormatsUntilEnd(
|
||||
SyntaxHighlighter *highlighter,
|
||||
const QFuture<Result> &future)
|
||||
{
|
||||
// find block number of last result
|
||||
int lastBlockNumber = 0;
|
||||
for (int i = future.resultCount() - 1; i >= 0; --i) {
|
||||
const Result &result = future.resultAt(i);
|
||||
if (result.line) {
|
||||
lastBlockNumber = result.line - 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
QTextDocument *doc = highlighter->document();
|
||||
QTC_ASSERT(lastBlockNumber + 1 < doc->blockCount(), return);
|
||||
QTextBlock b = doc->findBlockByNumber(lastBlockNumber + 1);
|
||||
|
||||
while (b.isValid()) {
|
||||
highlighter->setExtraAdditionalFormats(b, QList<QTextLayout::FormatRange>());
|
||||
b = b.next();
|
||||
}
|
||||
}
|
91
src/plugins/texteditor/semantichighlighter.h
Normal file
91
src/plugins/texteditor/semantichighlighter.h
Normal file
@@ -0,0 +1,91 @@
|
||||
/**************************************************************************
|
||||
**
|
||||
** This file is part of Qt Creator
|
||||
**
|
||||
** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
|
||||
**
|
||||
** Contact: Nokia Corporation (info@qt.nokia.com)
|
||||
**
|
||||
**
|
||||
** GNU Lesser General Public License Usage
|
||||
**
|
||||
** This file may be used under the terms of the GNU Lesser General Public
|
||||
** License version 2.1 as published by the Free Software Foundation and
|
||||
** appearing in the file LICENSE.LGPL included in the packaging of this file.
|
||||
** Please review the following information to ensure the GNU Lesser General
|
||||
** Public License version 2.1 requirements will be met:
|
||||
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
|
||||
**
|
||||
** In addition, as a special exception, Nokia gives you certain additional
|
||||
** rights. These rights are described in the Nokia Qt LGPL Exception
|
||||
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
|
||||
**
|
||||
** Other Usage
|
||||
**
|
||||
** Alternatively, this file may be used in accordance with the terms and
|
||||
** conditions contained in a signed written agreement between you and Nokia.
|
||||
**
|
||||
** If you have questions regarding the use of this file, please contact
|
||||
** Nokia at info@qt.nokia.com.
|
||||
**
|
||||
**************************************************************************/
|
||||
|
||||
#ifndef TEXTEDITOR_SEMANTICHIGHLIGHTER_H
|
||||
#define TEXTEDITOR_SEMANTICHIGHLIGHTER_H
|
||||
|
||||
#include "texteditor_global.h"
|
||||
|
||||
#include <QtCore/QHash>
|
||||
#include <QtCore/QMap>
|
||||
#include <QtCore/QVector>
|
||||
#include <QtCore/QFuture>
|
||||
#include <QtGui/QTextCharFormat>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
class QTextDocument;
|
||||
QT_END_NAMESPACE
|
||||
|
||||
namespace TextEditor {
|
||||
|
||||
class SyntaxHighlighter;
|
||||
|
||||
namespace SemanticHighlighter {
|
||||
|
||||
class TEXTEDITOR_EXPORT Result {
|
||||
public:
|
||||
unsigned line; // 1-based
|
||||
unsigned column; // 1-based
|
||||
unsigned length;
|
||||
int kind;
|
||||
|
||||
Result()
|
||||
: line(0), column(0), length(0), kind(-1) {}
|
||||
Result(unsigned line, unsigned column, unsigned length, int kind)
|
||||
: line(line), column(column), length(length), kind(kind) {}
|
||||
};
|
||||
|
||||
// Applies the future results [from, to) and applies the extra formats
|
||||
// indicated by Result::kind and kindToFormat to the correct location using
|
||||
// SyntaxHighlighter::setExtraAdditionalFormats.
|
||||
// It is incremental in the sense that it clears the extra additional formats
|
||||
// from all lines that have no results between the (from-1).line result and
|
||||
// the (to-1).line result.
|
||||
// Requires that results of the Future are ordered by line.
|
||||
void TEXTEDITOR_EXPORT incrementalApplyExtraAdditionalFormats(
|
||||
SyntaxHighlighter *highlighter,
|
||||
const QFuture<Result> &future,
|
||||
int from, int to,
|
||||
const QHash<int, QTextCharFormat> &kindToFormat);
|
||||
|
||||
// Cleans the extra additional formats after the last result of the Future
|
||||
// until the end of the document.
|
||||
// Requires that results of the Future are ordered by line.
|
||||
void TEXTEDITOR_EXPORT clearExtraAdditionalFormatsUntilEnd(
|
||||
SyntaxHighlighter *highlighter,
|
||||
const QFuture<Result> &future);
|
||||
|
||||
|
||||
} // namespace SemanticHighlighter
|
||||
} // namespace TextEditor
|
||||
|
||||
#endif // TEXTEDITOR_SEMANTICHIGHLIGHTER_H
|
@@ -106,7 +106,8 @@ SOURCES += texteditorplugin.cpp \
|
||||
tabpreferences.cpp \
|
||||
icodestylepreferencesfactory.cpp \
|
||||
tabpreferenceswidget.cpp \
|
||||
fallbackselectorwidget.cpp
|
||||
fallbackselectorwidget.cpp \
|
||||
semantichighlighter.cpp
|
||||
|
||||
HEADERS += texteditorplugin.h \
|
||||
textfilewizard.h \
|
||||
@@ -217,7 +218,8 @@ HEADERS += texteditorplugin.h \
|
||||
tabpreferences.h \
|
||||
icodestylepreferencesfactory.h \
|
||||
tabpreferenceswidget.h \
|
||||
fallbackselectorwidget.h
|
||||
fallbackselectorwidget.h \
|
||||
semantichighlighter.h
|
||||
|
||||
FORMS += \
|
||||
displaysettingspage.ui \
|
||||
|
@@ -128,6 +128,17 @@ const char * const C_COMMENT = "Comment";
|
||||
const char * const C_DOXYGEN_COMMENT = "Doxygen.Comment";
|
||||
const char * const C_DOXYGEN_TAG = "Doxygen.Tag";
|
||||
const char * const C_VISUAL_WHITESPACE = "VisualWhitespace";
|
||||
const char * const C_QML_LOCAL_ID = "QmlLocalId";
|
||||
const char * const C_QML_EXTERNAL_ID = "QmlExternalId";
|
||||
const char * const C_QML_TYPE_ID = "QmlTypeId";
|
||||
const char * const C_QML_ROOT_OBJECT_PROPERTY = "QmlRootObjectProperty";
|
||||
const char * const C_QML_SCOPE_OBJECT_PROPERTY = "QmlScopeObjectProperty";
|
||||
const char * const C_QML_EXTERNAL_OBJECT_PROPERTY = "QmlExternalObjectProperty";
|
||||
const char * const C_JS_SCOPE_VAR = "JsScopeVar";
|
||||
const char * const C_JS_IMPORT_VAR = "JsImportVar";
|
||||
const char * const C_JS_GLOBAL_VAR = "JsGlobalVar";
|
||||
const char * const C_QML_STATE_NAME = "QmlStateName";
|
||||
|
||||
|
||||
const char * const C_DISABLED_CODE = "DisabledCode";
|
||||
|
||||
|
@@ -146,6 +146,27 @@ TextEditorSettings::TextEditorSettings(QObject *parent)
|
||||
virtualMethodFormatDescriptor.format().setItalic(true);
|
||||
formatDescriptions.append(virtualMethodFormatDescriptor);
|
||||
|
||||
Format qmlLocalNameFormat;
|
||||
qmlLocalNameFormat.setItalic(true);
|
||||
formatDescriptions.append(FormatDescription(QLatin1String(C_QML_LOCAL_ID), tr("QML Local Id"), qmlLocalNameFormat));
|
||||
formatDescriptions.append(FormatDescription(QLatin1String(C_QML_ROOT_OBJECT_PROPERTY), tr("QML Root Object Property"), qmlLocalNameFormat));
|
||||
formatDescriptions.append(FormatDescription(QLatin1String(C_QML_SCOPE_OBJECT_PROPERTY), tr("QML Scope Object Property"), qmlLocalNameFormat));
|
||||
formatDescriptions.append(FormatDescription(QLatin1String(C_QML_STATE_NAME), tr("QML State Name"), qmlLocalNameFormat));
|
||||
|
||||
formatDescriptions.append(FormatDescription(QLatin1String(C_QML_TYPE_ID), tr("QML Type Name"), Qt::darkMagenta));
|
||||
|
||||
Format qmlExternalNameFormat = qmlLocalNameFormat;
|
||||
qmlExternalNameFormat.setForeground(Qt::darkBlue);
|
||||
formatDescriptions.append(FormatDescription(QLatin1String(C_QML_EXTERNAL_ID), tr("QML External Id"), qmlExternalNameFormat));
|
||||
formatDescriptions.append(FormatDescription(QLatin1String(C_QML_EXTERNAL_OBJECT_PROPERTY), tr("QML External Object Property"), qmlExternalNameFormat));
|
||||
|
||||
Format jsFormat;
|
||||
jsFormat.setForeground(QColor(Qt::darkCyan).darker());
|
||||
jsFormat.setItalic(true);
|
||||
formatDescriptions.append(FormatDescription(QLatin1String(C_JS_SCOPE_VAR), tr("JavaScript Scope Var"), jsFormat));
|
||||
formatDescriptions.append(FormatDescription(QLatin1String(C_JS_IMPORT_VAR), tr("JavaScript Import"), jsFormat));
|
||||
formatDescriptions.append(FormatDescription(QLatin1String(C_JS_GLOBAL_VAR), tr("JavaScript Global Var"), jsFormat));
|
||||
|
||||
formatDescriptions.append(FormatDescription(QLatin1String(C_KEYWORD), tr("Keyword"), Qt::darkYellow));
|
||||
formatDescriptions.append(FormatDescription(QLatin1String(C_OPERATOR), tr("Operator")));
|
||||
formatDescriptions.append(FormatDescription(QLatin1String(C_PREPROCESSOR), tr("Preprocessor"), Qt::darkBlue));
|
||||
|
Reference in New Issue
Block a user