Open type hierarchy from Type Hierarchy editor

Make it possible to open type hierarchy for different
class selected from Type Hierarchy editor.
Make it available under context menu or on double click
on class name (single click opens the class in cpp editor window
as before). Double click doesn't expand / collapse items anymore
(expanding available when pressing the visual arrow).
Make navigation to editor more up to date - e.g. when linked location
changed in meantime (source file was edited), it tries to find
linked symbol quickly again (we introduce a small delay, up to
100-200 ms, depending on source file).

Change-Id: Ifb4fd58e853589a17cd14be465b3a7695fa48193
Reviewed-by: Alessandro Portale <alessandro.portale@qt.io>
Reviewed-by: hjk <hjk@qt.io>
This commit is contained in:
Jarek Kobus
2020-12-18 14:40:18 +01:00
parent c427a0d281
commit 72f850df3a
4 changed files with 196 additions and 28 deletions

View File

@@ -43,6 +43,7 @@
#include <QApplication> #include <QApplication>
#include <QLabel> #include <QLabel>
#include <QLatin1String> #include <QLatin1String>
#include <QMenu>
#include <QModelIndex> #include <QModelIndex>
#include <QStackedLayout> #include <QStackedLayout>
#include <QVBoxLayout> #include <QVBoxLayout>
@@ -68,7 +69,7 @@ QStandardItem *itemForClass(const CppClass &cppClass)
item->setData(cppClass.qualifiedName, AnnotationRole); item->setData(cppClass.qualifiedName, AnnotationRole);
item->setData(cppClass.icon, Qt::DecorationRole); item->setData(cppClass.icon, Qt::DecorationRole);
QVariant link; QVariant link;
link.setValue(Utils::Link(cppClass.link)); link.setValue(Link(cppClass.link));
item->setData(link, LinkRole); item->setData(link, LinkRole);
return item; return item;
} }
@@ -76,7 +77,7 @@ QStandardItem *itemForClass(const CppClass &cppClass)
QList<CppClass> sortClasses(const QList<CppClass> &cppClasses) QList<CppClass> sortClasses(const QList<CppClass> &cppClasses)
{ {
QList<CppClass> sorted = cppClasses; QList<CppClass> sorted = cppClasses;
Utils::sort(sorted, [](const CppClass &c1, const CppClass &c2) -> bool { sort(sorted, [](const CppClass &c1, const CppClass &c2) -> bool {
const QString key1 = c1.name + QLatin1String("::") + c1.qualifiedName; const QString key1 = c1.name + QLatin1String("::") + c1.qualifiedName;
const QString key2 = c2.name + QLatin1String("::") + c2.qualifiedName; const QString key2 = c2.name + QLatin1String("::") + c2.qualifiedName;
return key1 < key2; return key1 < key2;
@@ -86,6 +87,49 @@ QList<CppClass> sortClasses(const QList<CppClass> &cppClasses)
} // Anonymous } // Anonymous
class CppTypeHierarchyTreeView : public NavigationTreeView
{
Q_OBJECT
public:
CppTypeHierarchyTreeView(QWidget *parent);
void contextMenuEvent(QContextMenuEvent *event) override;
};
CppTypeHierarchyTreeView::CppTypeHierarchyTreeView(QWidget *parent) :
NavigationTreeView(parent)
{
}
void CppTypeHierarchyTreeView::contextMenuEvent(QContextMenuEvent *event)
{
if (!event)
return;
QMenu contextMenu;
QAction *action = contextMenu.addAction(tr("Open in Editor"));
connect(action, &QAction::triggered, this, [this] () {
emit activated(currentIndex());
});
action = contextMenu.addAction(tr("Open Type Hierarchy"));
connect(action, &QAction::triggered, this, [this] () {
emit doubleClicked(currentIndex());
});
contextMenu.addSeparator();
action = contextMenu.addAction(tr("Expand All"));
connect(action, &QAction::triggered, this, &QTreeView::expandAll);
action = contextMenu.addAction(tr("Collapse All"));
connect(action, &QAction::triggered, this, &QTreeView::collapseAll);
contextMenu.exec(event->globalPos());
event->accept();
}
namespace CppEditor { namespace CppEditor {
namespace Internal { namespace Internal {
@@ -95,12 +139,13 @@ CppTypeHierarchyWidget::CppTypeHierarchyWidget()
m_inspectedClass = new TextEditor::TextEditorLinkLabel(this); m_inspectedClass = new TextEditor::TextEditorLinkLabel(this);
m_inspectedClass->setContentsMargins(5, 5, 5, 5); m_inspectedClass->setContentsMargins(5, 5, 5, 5);
m_model = new CppTypeHierarchyModel(this); m_model = new CppTypeHierarchyModel(this);
m_treeView = new NavigationTreeView(this); m_treeView = new CppTypeHierarchyTreeView(this);
m_treeView->setActivationMode(SingleClickActivation); m_treeView->setActivationMode(SingleClickActivation);
m_delegate = new AnnotatedItemDelegate(this); m_delegate = new AnnotatedItemDelegate(this);
m_delegate->setDelimiter(QLatin1String(" ")); m_delegate->setDelimiter(QLatin1String(" "));
m_delegate->setAnnotationRole(AnnotationRole); m_delegate->setAnnotationRole(AnnotationRole);
m_treeView->setModel(m_model); m_treeView->setModel(m_model);
m_treeView->setExpandsOnDoubleClick(false);
m_treeView->setEditTriggers(QAbstractItemView::NoEditTriggers); m_treeView->setEditTriggers(QAbstractItemView::NoEditTriggers);
m_treeView->setItemDelegate(m_delegate); m_treeView->setItemDelegate(m_delegate);
m_treeView->setRootIsDecorated(false); m_treeView->setRootIsDecorated(false);
@@ -108,6 +153,7 @@ CppTypeHierarchyWidget::CppTypeHierarchyWidget()
m_treeView->setDragDropMode(QAbstractItemView::DragOnly); m_treeView->setDragDropMode(QAbstractItemView::DragOnly);
m_treeView->setDefaultDropAction(Qt::MoveAction); m_treeView->setDefaultDropAction(Qt::MoveAction);
connect(m_treeView, &QTreeView::activated, this, &CppTypeHierarchyWidget::onItemActivated); connect(m_treeView, &QTreeView::activated, this, &CppTypeHierarchyWidget::onItemActivated);
connect(m_treeView, &QTreeView::doubleClicked, this, &CppTypeHierarchyWidget::onItemDoubleClicked);
m_infoLabel = new QLabel(this); m_infoLabel = new QLabel(this);
m_infoLabel->setAlignment(Qt::AlignCenter); m_infoLabel->setAlignment(Qt::AlignCenter);
@@ -179,6 +225,26 @@ void CppTypeHierarchyWidget::perform()
Core::ProgressManager::addTask(m_future, tr("Evaluating Type Hierarchy"), "TypeHierarchy"); Core::ProgressManager::addTask(m_future, tr("Evaluating Type Hierarchy"), "TypeHierarchy");
} }
void CppTypeHierarchyWidget::performFromExpression(const QString &expression, const QString &fileName)
{
if (m_future.isRunning())
m_future.cancel();
updateSynchronizer();
showProgress();
CppElementEvaluator evaluator(nullptr);
evaluator.setLookupBaseClasses(true);
evaluator.setLookupDerivedClasses(true);
evaluator.setExpression(expression, fileName);
m_future = evaluator.asyncExpressionExecute();
m_futureWatcher.setFuture(QFuture<void>(m_future));
m_synchronizer.addFuture(QFuture<void>(m_future));
Core::ProgressManager::addTask(m_future, tr("Evaluating Type Hierarchy"), "TypeHierarchy");
}
void CppTypeHierarchyWidget::displayHierarchy() void CppTypeHierarchyWidget::displayHierarchy()
{ {
updateSynchronizer(); updateSynchronizer();
@@ -240,7 +306,7 @@ void CppTypeHierarchyWidget::showProgress()
{ {
m_infoLabel->setText(tr("Evaluating type hierarchy...")); m_infoLabel->setText(tr("Evaluating type hierarchy..."));
if (!m_progressIndicator) { if (!m_progressIndicator) {
m_progressIndicator = new Utils::ProgressIndicator(Utils::ProgressIndicatorSize::Large); m_progressIndicator = new ProgressIndicator(ProgressIndicatorSize::Large);
m_progressIndicator->attachToWidget(this); m_progressIndicator->attachToWidget(this);
} }
m_progressIndicator->show(); m_progressIndicator->show();
@@ -258,14 +324,36 @@ void CppTypeHierarchyWidget::clearTypeHierarchy()
m_model->clear(); m_model->clear();
} }
static QString getExpression(const QModelIndex &index)
{
const QString annotation = index.data(AnnotationRole).toString();
if (!annotation.isEmpty())
return annotation;
return index.data(Qt::DisplayRole).toString();
}
void CppTypeHierarchyWidget::onItemActivated(const QModelIndex &index) void CppTypeHierarchyWidget::onItemActivated(const QModelIndex &index)
{ {
auto link = index.data(LinkRole).value<Utils::Link>(); auto link = index.data(LinkRole).value<Link>();
if (!link.hasValidTarget())
return;
const Link updatedLink = CppElementEvaluator::linkFromExpression(
getExpression(index), link.targetFileName);
if (updatedLink.hasValidTarget())
link = updatedLink;
Core::EditorManager::openEditorAt(link.targetFileName,
link.targetLine,
link.targetColumn,
Constants::CPPEDITOR_ID);
}
void CppTypeHierarchyWidget::onItemDoubleClicked(const QModelIndex &index)
{
const auto link = index.data(LinkRole).value<Link>();
if (link.hasValidTarget()) if (link.hasValidTarget())
Core::EditorManager::openEditorAt(link.targetFileName, performFromExpression(getExpression(index), link.targetFileName);
link.targetLine,
link.targetColumn,
Constants::CPPEDITOR_ID);
} }
// CppTypeHierarchyFactory // CppTypeHierarchyFactory
@@ -307,7 +395,7 @@ QMimeData *CppTypeHierarchyModel::mimeData(const QModelIndexList &indexes) const
auto data = new DropMimeData; auto data = new DropMimeData;
data->setOverrideFileDropAction(Qt::CopyAction); // do not remove the item from the model data->setOverrideFileDropAction(Qt::CopyAction); // do not remove the item from the model
foreach (const QModelIndex &index, indexes) { foreach (const QModelIndex &index, indexes) {
auto link = index.data(LinkRole).value<Utils::Link>(); auto link = index.data(LinkRole).value<Link>();
if (link.hasValidTarget()) if (link.hasValidTarget())
data->addFile(link.targetFileName, link.targetLine, link.targetColumn); data->addFile(link.targetFileName, link.targetLine, link.targetColumn);
} }
@@ -317,3 +405,4 @@ QMimeData *CppTypeHierarchyModel::mimeData(const QModelIndexList &indexes) const
} // namespace Internal } // namespace Internal
} // namespace CppEditor } // namespace CppEditor
#include "cpptypehierarchy.moc"

View File

@@ -84,6 +84,7 @@ private slots:
private: private:
typedef QList<CppTools::CppClass> CppTools::CppClass::*HierarchyMember; typedef QList<CppTools::CppClass> CppTools::CppClass::*HierarchyMember;
void performFromExpression(const QString &expression, const QString &fileName);
void buildHierarchy(const CppTools::CppClass &cppClass, QStandardItem *parent, void buildHierarchy(const CppTools::CppClass &cppClass, QStandardItem *parent,
bool isRoot, HierarchyMember member); bool isRoot, HierarchyMember member);
void showNoTypeHierarchyLabel(); void showNoTypeHierarchyLabel();
@@ -92,6 +93,7 @@ private:
void hideProgress(); void hideProgress();
void clearTypeHierarchy(); void clearTypeHierarchy();
void onItemActivated(const QModelIndex &index); void onItemActivated(const QModelIndex &index);
void onItemDoubleClicked(const QModelIndex &index);
void updateSynchronizer(); void updateSynchronizer();
CppEditorWidget *m_cppEditor = nullptr; CppEditorWidget *m_cppEditor = nullptr;

View File

@@ -350,7 +350,7 @@ public:
CppElementEvaluator::CppElementEvaluator(TextEditor::TextEditorWidget *editor) : CppElementEvaluator::CppElementEvaluator(TextEditor::TextEditorWidget *editor) :
m_editor(editor), m_editor(editor),
m_modelManager(CppModelManager::instance()), m_modelManager(CppModelManager::instance()),
m_tc(editor->textCursor()), m_tc(editor ? editor->textCursor() : QTextCursor()),
m_lookupBaseClasses(false), m_lookupBaseClasses(false),
m_lookupDerivedClasses(false) m_lookupDerivedClasses(false)
{} {}
@@ -364,6 +364,12 @@ void CppElementEvaluator::setLookupBaseClasses(const bool lookup)
void CppElementEvaluator::setLookupDerivedClasses(const bool lookup) void CppElementEvaluator::setLookupDerivedClasses(const bool lookup)
{ m_lookupDerivedClasses = lookup; } { m_lookupDerivedClasses = lookup; }
void CppElementEvaluator::setExpression(const QString &expression, const QString &fileName)
{
m_expression = expression;
m_fileName = fileName;
}
// special case for bug QTCREATORBUG-4780 // special case for bug QTCREATORBUG-4780
static bool shouldOmitElement(const LookupItem &lookupItem, const Scope *scope) static bool shouldOmitElement(const LookupItem &lookupItem, const Scope *scope)
{ {
@@ -373,12 +379,17 @@ static bool shouldOmitElement(const LookupItem &lookupItem, const Scope *scope)
void CppElementEvaluator::execute() void CppElementEvaluator::execute()
{ {
execute(&CppElementEvaluator::syncExec); execute(&CppElementEvaluator::sourceDataFromGui, &CppElementEvaluator::syncExec);
} }
QFuture<QSharedPointer<CppElement>> CppElementEvaluator::asyncExecute() QFuture<QSharedPointer<CppElement>> CppElementEvaluator::asyncExecute()
{ {
return execute(&CppElementEvaluator::asyncExec); return execute(&CppElementEvaluator::sourceDataFromGui, &CppElementEvaluator::asyncExec);
}
QFuture<QSharedPointer<CppElement>> CppElementEvaluator::asyncExpressionExecute()
{
return execute(&CppElementEvaluator::sourceDataFromExpression, &CppElementEvaluator::asyncExec);
} }
static QFuture<QSharedPointer<CppElement>> createFinishedFuture() static QFuture<QSharedPointer<CppElement>> createFinishedFuture()
@@ -389,18 +400,12 @@ static QFuture<QSharedPointer<CppElement>> createFinishedFuture()
return futureInterface.future(); return futureInterface.future();
} }
// @todo: Consider refactoring code from CppEditor::findLinkAt into here. bool CppElementEvaluator::sourceDataFromGui(const CPlusPlus::Snapshot &snapshot,
QFuture<QSharedPointer<CppElement>> CppElementEvaluator::execute(ExecFunction execFuntion) Document::Ptr &doc, Scope **scope, QString &expression)
{ {
clear(); doc = snapshot.document(m_editor->textDocument()->filePath());
if (!m_modelManager)
return createFinishedFuture();
const Snapshot &snapshot = m_modelManager->snapshot();
Document::Ptr doc = snapshot.document(m_editor->textDocument()->filePath());
if (!doc) if (!doc)
return createFinishedFuture(); return false;
int line = 0; int line = 0;
int column = 0; int column = 0;
@@ -410,14 +415,44 @@ QFuture<QSharedPointer<CppElement>> CppElementEvaluator::execute(ExecFunction ex
checkDiagnosticMessage(pos); checkDiagnosticMessage(pos);
if (matchIncludeFile(doc, line) || matchMacroInUse(doc, pos)) if (matchIncludeFile(doc, line) || matchMacroInUse(doc, pos))
return createFinishedFuture(); return false;
CppTools::moveCursorToEndOfIdentifier(&m_tc); CppTools::moveCursorToEndOfIdentifier(&m_tc);
ExpressionUnderCursor expressionUnderCursor(doc->languageFeatures());
expression = expressionUnderCursor(m_tc);
// Fetch the expression's code // Fetch the expression's code
ExpressionUnderCursor expressionUnderCursor(doc->languageFeatures()); *scope = doc->scopeAt(line, column - 1);
const QString &expression = expressionUnderCursor(m_tc); return true;
Scope *scope = doc->scopeAt(line, column - 1); }
bool CppElementEvaluator::sourceDataFromExpression(const CPlusPlus::Snapshot &snapshot,
Document::Ptr &doc, Scope **scope, QString &expression)
{
doc = snapshot.document(m_fileName);
expression = m_expression;
// Fetch the expression's code
*scope = doc->globalNamespace();
return true;
}
// @todo: Consider refactoring code from CppEditor::findLinkAt into here.
QFuture<QSharedPointer<CppElement>> CppElementEvaluator::execute(SourceFunction sourceFunction,
ExecFunction execFuntion)
{
clear();
if (!m_modelManager)
return createFinishedFuture();
const Snapshot &snapshot = m_modelManager->snapshot();
Document::Ptr doc;
QString expression;
Scope *scope = nullptr;
if (!std::invoke(sourceFunction, this, snapshot, doc, &scope, expression))
return createFinishedFuture();
TypeOfExpression typeOfExpression; TypeOfExpression typeOfExpression;
typeOfExpression.init(doc, snapshot); typeOfExpression.init(doc, snapshot);
@@ -589,4 +624,28 @@ void CppElementEvaluator::clear()
m_diagnosis.clear(); m_diagnosis.clear();
} }
Utils::Link CppElementEvaluator::linkFromExpression(const QString &expression, const QString &fileName)
{
const Snapshot &snapshot = CppModelManager::instance()->snapshot();
Document::Ptr doc = snapshot.document(fileName);
Scope *scope = doc->globalNamespace();
TypeOfExpression typeOfExpression;
typeOfExpression.init(doc, snapshot);
typeOfExpression.setExpandTemplates(true);
const QList<LookupItem> &lookupItems = typeOfExpression(expression.toUtf8(), scope);
if (lookupItems.isEmpty())
return Utils::Link();
for (const LookupItem &item : lookupItems) {
Symbol *symbol = item.declaration();
if (!symbol)
continue;
if (!symbol->isClass() && !symbol->isTemplate())
continue;
return symbol->toLink();
}
return Utils::Link();
}
} // namespace CppTools } // namespace CppTools

View File

@@ -56,25 +56,41 @@ public:
void setTextCursor(const QTextCursor &tc); void setTextCursor(const QTextCursor &tc);
void setLookupBaseClasses(const bool lookup); void setLookupBaseClasses(const bool lookup);
void setLookupDerivedClasses(const bool lookup); void setLookupDerivedClasses(const bool lookup);
void setExpression(const QString &expression, const QString &fileName);
void execute(); void execute();
QFuture<QSharedPointer<CppElement>> asyncExecute(); QFuture<QSharedPointer<CppElement>> asyncExecute();
QFuture<QSharedPointer<CppElement>> asyncExpressionExecute();
bool identifiedCppElement() const; bool identifiedCppElement() const;
const QSharedPointer<CppElement> &cppElement() const; const QSharedPointer<CppElement> &cppElement() const;
bool hasDiagnosis() const; bool hasDiagnosis() const;
const QString &diagnosis() const; const QString &diagnosis() const;
static Utils::Link linkFromExpression(const QString &expression, const QString &fileName);
private: private:
void clear(); void clear();
using ExecFunction = QFuture<QSharedPointer<CppElement>>(CppElementEvaluator::*) using ExecFunction = QFuture<QSharedPointer<CppElement>>(CppElementEvaluator::*)
(const CPlusPlus::Snapshot &, const CPlusPlus::LookupItem &, (const CPlusPlus::Snapshot &, const CPlusPlus::LookupItem &,
const CPlusPlus::LookupContext &); const CPlusPlus::LookupContext &);
using SourceFunction = bool(CppElementEvaluator::*)
(const CPlusPlus::Snapshot &, CPlusPlus::Document::Ptr &,
CPlusPlus::Scope **, QString &);
QFuture<QSharedPointer<CppElement>> execute(ExecFunction execFuntion); QFuture<QSharedPointer<CppElement>> execute(SourceFunction sourceFunction,
ExecFunction execFuntion);
QFuture<QSharedPointer<CppElement>> syncExec(const CPlusPlus::Snapshot &, QFuture<QSharedPointer<CppElement>> syncExec(const CPlusPlus::Snapshot &,
const CPlusPlus::LookupItem &, const CPlusPlus::LookupContext &); const CPlusPlus::LookupItem &, const CPlusPlus::LookupContext &);
QFuture<QSharedPointer<CppElement>> asyncExec(const CPlusPlus::Snapshot &, QFuture<QSharedPointer<CppElement>> asyncExec(const CPlusPlus::Snapshot &,
const CPlusPlus::LookupItem &, const CPlusPlus::LookupContext &); const CPlusPlus::LookupItem &, const CPlusPlus::LookupContext &);
bool sourceDataFromGui(const CPlusPlus::Snapshot &snapshot,
CPlusPlus::Document::Ptr &doc,
CPlusPlus::Scope **scope,
QString &expression);
bool sourceDataFromExpression(const CPlusPlus::Snapshot &snapshot,
CPlusPlus::Document::Ptr &doc,
CPlusPlus::Scope **scope,
QString &expression);
void checkDiagnosticMessage(int pos); void checkDiagnosticMessage(int pos);
bool matchIncludeFile(const CPlusPlus::Document::Ptr &document, int line); bool matchIncludeFile(const CPlusPlus::Document::Ptr &document, int line);
bool matchMacroInUse(const CPlusPlus::Document::Ptr &document, int pos); bool matchMacroInUse(const CPlusPlus::Document::Ptr &document, int pos);
@@ -82,6 +98,8 @@ private:
TextEditor::TextEditorWidget *m_editor; TextEditor::TextEditorWidget *m_editor;
CppTools::CppModelManager *m_modelManager; CppTools::CppModelManager *m_modelManager;
QTextCursor m_tc; QTextCursor m_tc;
QString m_expression;
QString m_fileName;
bool m_lookupBaseClasses; bool m_lookupBaseClasses;
bool m_lookupDerivedClasses; bool m_lookupDerivedClasses;
QSharedPointer<CppElement> m_element; QSharedPointer<CppElement> m_element;