forked from qt-creator/qt-creator
Debugger: Add an option to show simple values as text annotations
Change-Id: I726d8559d7e28abd776ce483d5f670be5af09412 Reviewed-by: André Hartmann <aha_1980@gmx.de> Reviewed-by: David Schulz <david.schulz@qt.io>
This commit is contained in:
@@ -83,6 +83,9 @@ public:
|
||||
auto checkBoxUseToolTipsInMainEditor = new QCheckBox(behaviorBox);
|
||||
checkBoxUseToolTipsInMainEditor->setText(tr("Use tooltips in main editor while debugging"));
|
||||
|
||||
auto checkBoxUseAnnotationsInMainEditor = new QCheckBox(behaviorBox);
|
||||
checkBoxUseAnnotationsInMainEditor->setText(tr("Use annotations in main editor while debugging"));
|
||||
|
||||
QString t = tr("Stopping and stepping in the debugger "
|
||||
"will automatically open views associated with the current location.") + '\n';
|
||||
auto checkBoxCloseSourceBuffersOnExit = new QCheckBox(behaviorBox);
|
||||
@@ -146,13 +149,14 @@ public:
|
||||
|
||||
auto gridLayout = new QGridLayout(behaviorBox);
|
||||
gridLayout->addWidget(checkBoxUseAlternatingRowColors, 0, 0, 1, 1);
|
||||
gridLayout->addWidget(checkBoxUseToolTipsInMainEditor, 1, 0, 1, 1);
|
||||
gridLayout->addWidget(checkBoxCloseSourceBuffersOnExit, 2, 0, 1, 1);
|
||||
gridLayout->addWidget(checkBoxCloseMemoryBuffersOnExit, 3, 0, 1, 1);
|
||||
gridLayout->addWidget(checkBoxBringToForegroundOnInterrrupt, 4, 0, 1, 1);
|
||||
gridLayout->addWidget(checkBoxBreakpointsFullPath, 5, 0, 1, 1);
|
||||
gridLayout->addWidget(checkBoxWarnOnReleaseBuilds, 6, 0, 1, 1);
|
||||
gridLayout->addLayout(horizontalLayout, 7, 0, 1, 2);
|
||||
gridLayout->addWidget(checkBoxUseAnnotationsInMainEditor, 1, 0, 1, 1);
|
||||
gridLayout->addWidget(checkBoxUseToolTipsInMainEditor, 2, 0, 1, 1);
|
||||
gridLayout->addWidget(checkBoxCloseSourceBuffersOnExit, 3, 0, 1, 1);
|
||||
gridLayout->addWidget(checkBoxCloseMemoryBuffersOnExit, 4, 0, 1, 1);
|
||||
gridLayout->addWidget(checkBoxBringToForegroundOnInterrrupt, 5, 0, 1, 1);
|
||||
gridLayout->addWidget(checkBoxBreakpointsFullPath, 6, 0, 1, 1);
|
||||
gridLayout->addWidget(checkBoxWarnOnReleaseBuilds, 7, 0, 1, 1);
|
||||
gridLayout->addLayout(horizontalLayout, 8, 0, 1, 2);
|
||||
|
||||
gridLayout->addWidget(checkBoxFontSizeFollowsEditor, 0, 1, 1, 1);
|
||||
gridLayout->addWidget(checkBoxSwitchModeOnExit, 1, 1, 1, 1);
|
||||
@@ -167,6 +171,8 @@ public:
|
||||
|
||||
m_group.insert(action(UseAlternatingRowColors),
|
||||
checkBoxUseAlternatingRowColors);
|
||||
m_group.insert(action(UseAnnotationsInMainEditor),
|
||||
checkBoxUseAnnotationsInMainEditor);
|
||||
m_group.insert(action(UseToolTipsInMainEditor),
|
||||
checkBoxUseToolTipsInMainEditor);
|
||||
m_group.insert(action(CloseSourceBuffersOnExit),
|
||||
|
@@ -497,6 +497,15 @@ DebuggerSettings::DebuggerSettings()
|
||||
item->setDefaultValue(false);
|
||||
insertItem(IntelFlavor, item);
|
||||
|
||||
item = new SavedAction;
|
||||
item->setSettingsKey(debugModeGroup, "UseAnnotations");
|
||||
item->setText(tr("Use annotations in main editor when debugging"));
|
||||
item->setToolTip(tr("<p>Checking this will show simple variable values "
|
||||
"as annotations in the main editor during debugging."));
|
||||
item->setCheckable(true);
|
||||
item->setDefaultValue(true);
|
||||
insertItem(UseAnnotationsInMainEditor, item);
|
||||
|
||||
item = new SavedAction;
|
||||
item->setSettingsKey(debugModeGroup, "UseToolTips");
|
||||
item->setText(tr("Use tooltips in main editor when debugging"));
|
||||
|
@@ -98,6 +98,7 @@ enum DebuggerActionCode
|
||||
ShowThreadNames,
|
||||
|
||||
UseToolTipsInMainEditor,
|
||||
UseAnnotationsInMainEditor,
|
||||
UseToolTipsInLocalsView,
|
||||
UseToolTipsInBreakpointsView,
|
||||
UseToolTipsInStackView,
|
||||
|
@@ -1113,6 +1113,7 @@ void DebuggerEngine::gotoLocation(const Location &loc)
|
||||
}
|
||||
|
||||
d->m_breakHandler.setLocation(loc);
|
||||
d->m_watchHandler.setLocation(loc);
|
||||
}
|
||||
|
||||
void DebuggerEngine::gotoCurrentLocation()
|
||||
|
@@ -68,6 +68,7 @@ const char TASK_CATEGORY_DEBUGGER_RUNTIME[] = "DebugRuntime";
|
||||
|
||||
const char TEXT_MARK_CATEGORY_BREAKPOINT[] = "Debugger.Mark.Breakpoint";
|
||||
const char TEXT_MARK_CATEGORY_LOCATION[] = "Debugger.Mark.Location";
|
||||
const char TEXT_MARK_CATEGORY_VALUE[] = "Debugger.Mark.Value";
|
||||
|
||||
const char OPENED_BY_DEBUGGER[] = "OpenedByDebugger";
|
||||
const char OPENED_WITH_DISASSEMBLY[] = "DisassemblerView";
|
||||
|
@@ -31,14 +31,19 @@
|
||||
#include "watchdata.h"
|
||||
#include "watchutils.h"
|
||||
|
||||
#include <texteditor/texteditor.h>
|
||||
#include <texteditor/textdocument.h>
|
||||
#include <coreplugin/editormanager/ieditor.h>
|
||||
|
||||
#include <cplusplus/CppDocument.h>
|
||||
#include <cplusplus/ExpressionUnderCursor.h>
|
||||
#include <cplusplus/LookupItem.h>
|
||||
#include <cplusplus/Overview.h>
|
||||
|
||||
#include <cpptools/abstracteditorsupport.h>
|
||||
#include <cpptools/cppprojectfile.h>
|
||||
#include <cpptools/cppmodelmanager.h>
|
||||
#include <cplusplus/CppDocument.h>
|
||||
#include <cplusplus/ExpressionUnderCursor.h>
|
||||
#include <cplusplus/Overview.h>
|
||||
|
||||
#include <texteditor/texteditor.h>
|
||||
#include <texteditor/textdocument.h>
|
||||
|
||||
#include <utils/qtcassert.h>
|
||||
|
||||
@@ -54,6 +59,7 @@ enum { debug = 0 };
|
||||
using namespace CppTools;
|
||||
using namespace CPlusPlus;
|
||||
using namespace TextEditor;
|
||||
using namespace Utils;
|
||||
|
||||
namespace CPlusPlus {
|
||||
|
||||
@@ -343,5 +349,95 @@ ContextData getLocationContext(TextDocument *document, int lineNumber)
|
||||
return data;
|
||||
}
|
||||
|
||||
//
|
||||
// Annotations
|
||||
//
|
||||
class DebuggerValueMark : public TextEditor::TextMark
|
||||
{
|
||||
public:
|
||||
DebuggerValueMark(const FilePath &fileName, int lineNumber, const QString &value)
|
||||
: TextMark(fileName, lineNumber, Constants::TEXT_MARK_CATEGORY_VALUE)
|
||||
{
|
||||
setPriority(TextEditor::TextMark::HighPriority);
|
||||
setToolTipProvider([] { return QString(); });
|
||||
setLineAnnotation(value);
|
||||
}
|
||||
};
|
||||
|
||||
static QList<DebuggerValueMark *> marks;
|
||||
|
||||
// Stolen from CPlusPlus::Document::functionAt(...)
|
||||
static int firstRelevantLine(const Document::Ptr document, int line, int column)
|
||||
{
|
||||
QTC_ASSERT(line > 0 && column > 0, return 0);
|
||||
CPlusPlus::Symbol *symbol = document->lastVisibleSymbolAt(line, column);
|
||||
if (!symbol)
|
||||
return 0;
|
||||
|
||||
// Find the enclosing function scope (which might be several levels up,
|
||||
// or we might be standing on it)
|
||||
Scope *scope = symbol->asScope();
|
||||
if (!scope)
|
||||
scope = symbol->enclosingScope();
|
||||
|
||||
while (scope && !scope->isFunction() )
|
||||
scope = scope->enclosingScope();
|
||||
|
||||
if (!scope)
|
||||
return 0;
|
||||
|
||||
return scope->line();
|
||||
}
|
||||
|
||||
static void setValueAnnotationsHelper(BaseTextEditor *textEditor,
|
||||
const Location &loc,
|
||||
QMap<QString, QString> values)
|
||||
{
|
||||
TextEditorWidget *widget = textEditor->editorWidget();
|
||||
TextDocument *textDocument = widget->textDocument();
|
||||
const FilePath filePath = loc.fileName();
|
||||
const Snapshot snapshot = CppModelManager::instance()->snapshot();
|
||||
const Document::Ptr cppDocument = snapshot.document(filePath.toString());
|
||||
if (!cppDocument) // For non-C++ documents.
|
||||
return;
|
||||
|
||||
const int firstLine = firstRelevantLine(cppDocument, loc.lineNumber(), 1);
|
||||
if (firstLine < 1)
|
||||
return;
|
||||
|
||||
CPlusPlus::ExpressionUnderCursor expressionUnderCursor(cppDocument->languageFeatures());
|
||||
QTextCursor tc = widget->textCursor();
|
||||
for (int lineNumber = loc.lineNumber(); lineNumber >= firstLine; --lineNumber) {
|
||||
const QTextBlock block = textDocument->document()->findBlockByNumber(lineNumber - 1);
|
||||
tc.setPosition(block.position());
|
||||
for (; !tc.atBlockEnd(); tc.movePosition(QTextCursor::NextCharacter)) {
|
||||
const QString expression = expressionUnderCursor(tc);
|
||||
if (expression.isEmpty())
|
||||
continue;
|
||||
const QString value = values.take(expression); // Show value one only once.
|
||||
if (value.isEmpty())
|
||||
continue;
|
||||
const QString annotation = QString("%1: %2").arg(expression, value);
|
||||
marks.append(new DebuggerValueMark(filePath, lineNumber, annotation));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void setValueAnnotations(const Location &loc, const QMap<QString, QString> &values)
|
||||
{
|
||||
qDeleteAll(marks);
|
||||
marks.clear();
|
||||
if (values.isEmpty())
|
||||
return;
|
||||
|
||||
const QList<Core::IEditor *> editors = Core::EditorManager::visibleEditors();
|
||||
for (Core::IEditor *editor : editors) {
|
||||
if (auto textEditor = qobject_cast<BaseTextEditor *>(editor)) {
|
||||
if (textEditor->textDocument()->filePath() == loc.fileName())
|
||||
setValueAnnotationsHelper(textEditor, loc, values);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Internal
|
||||
} // namespace Debugger
|
||||
|
@@ -25,6 +25,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QMap>
|
||||
#include <QString>
|
||||
|
||||
namespace TextEditor {
|
||||
@@ -38,6 +39,7 @@ namespace Debugger {
|
||||
namespace Internal {
|
||||
|
||||
class ContextData;
|
||||
class Location;
|
||||
|
||||
// Editor tooltip support
|
||||
QString cppExpressionAt(TextEditor::TextEditorWidget *editorWidget, int pos,
|
||||
@@ -54,5 +56,7 @@ QStringList getUninitializedVariables(const CPlusPlus::Snapshot &snapshot,
|
||||
|
||||
ContextData getLocationContext(TextEditor::TextDocument *document, int lineNumber);
|
||||
|
||||
void setValueAnnotations(const Location &loc, const QMap<QString, QString> &values);
|
||||
|
||||
} // namespace Internal
|
||||
} // namespace Debugger
|
||||
|
@@ -520,6 +520,7 @@ QString WatchItem::toToolTip() const
|
||||
formatToolTipRow(str, tr("Static Object Size"), tr("%n bytes", nullptr, size));
|
||||
formatToolTipRow(str, tr("Internal ID"), internalName());
|
||||
formatToolTipRow(str, tr("Creation Time in ms"), QString::number(int(time * 1000)));
|
||||
formatToolTipRow(str, tr("Source"), sourceExpression());
|
||||
str << "</table></body></html>";
|
||||
return res;
|
||||
}
|
||||
@@ -578,6 +579,31 @@ QString WatchItem::expression() const
|
||||
return name;
|
||||
}
|
||||
|
||||
QString WatchItem::sourceExpression() const
|
||||
{
|
||||
const WatchItem *p = parent();
|
||||
if (!p)
|
||||
return {}; // Root
|
||||
|
||||
const WatchItem *pp = p->parent();
|
||||
if (!pp)
|
||||
return {}; // local
|
||||
|
||||
const WatchItem *ppp = pp->parent();
|
||||
if (!ppp)
|
||||
return name; // local.x -> 'x'
|
||||
|
||||
// Enforce some arbitrary, but fixed limit to avoid excessive creation
|
||||
// of very likely unused strings which are for convenience only.
|
||||
if (arrayIndex >= 0 && arrayIndex <= 16)
|
||||
return QString("%1[%2]").arg(p->sourceExpression()).arg(arrayIndex);
|
||||
|
||||
if (p->name == '*')
|
||||
return QString("%1->%2").arg(pp->sourceExpression(), name);
|
||||
|
||||
return QString("%1.%2").arg(p->sourceExpression(), name);
|
||||
}
|
||||
|
||||
} // namespace Internal
|
||||
} // namespace Debugger
|
||||
|
||||
|
@@ -51,6 +51,7 @@ public:
|
||||
bool isInspect() const;
|
||||
|
||||
QString expression() const;
|
||||
QString sourceExpression() const;
|
||||
QString realName() const;
|
||||
QString internalName() const;
|
||||
QString toToolTip() const;
|
||||
|
@@ -38,6 +38,7 @@
|
||||
#include "memoryagent.h"
|
||||
#include "registerhandler.h"
|
||||
#include "simplifytype.h"
|
||||
#include "sourceutils.h"
|
||||
#include "watchdelegatewidgets.h"
|
||||
#include "watchutils.h"
|
||||
|
||||
@@ -67,6 +68,7 @@
|
||||
#include <QJsonArray>
|
||||
#include <QJsonObject>
|
||||
#include <QLabel>
|
||||
#include <QMap>
|
||||
#include <QMenu>
|
||||
#include <QMimeData>
|
||||
#include <QPainter>
|
||||
@@ -473,6 +475,8 @@ public:
|
||||
QHash<QString, TypeInfo> m_reportedTypeInfo;
|
||||
QHash<QString, DisplayFormats> m_reportedTypeFormats; // Type name -> Dumper Formats
|
||||
QHash<QString, QString> m_valueCache;
|
||||
|
||||
Location m_location;
|
||||
};
|
||||
|
||||
WatchModel::WatchModel(WatchHandler *handler, DebuggerEngine *engine)
|
||||
@@ -518,6 +522,8 @@ WatchModel::WatchModel(WatchHandler *handler, DebuggerEngine *engine)
|
||||
m_engine, &DebuggerEngine::updateAll);
|
||||
connect(action(ShowQObjectNames), &SavedAction::valueChanged,
|
||||
m_engine, &DebuggerEngine::updateAll);
|
||||
connect(action(UseAnnotationsInMainEditor), &SavedAction::valueChanged,
|
||||
m_engine, &DebuggerEngine::updateAll);
|
||||
|
||||
connect(SessionManager::instance(), &SessionManager::sessionLoaded,
|
||||
this, &loadSessionData);
|
||||
@@ -2081,6 +2087,7 @@ void WatchHandler::cleanup()
|
||||
theTemporaryWatchers.clear();
|
||||
saveWatchers();
|
||||
m_model->reinitialize();
|
||||
Internal::setValueAnnotations(m_model->m_location, {});
|
||||
emit m_model->updateFinished();
|
||||
m_model->m_separatedView->hide();
|
||||
}
|
||||
@@ -2234,6 +2241,16 @@ void WatchHandler::notifyUpdateFinished()
|
||||
}
|
||||
});
|
||||
|
||||
QMap<QString, QString> values;
|
||||
if (boolSetting(UseAnnotationsInMainEditor)) {
|
||||
m_model->forAllItems([&values](WatchItem *item) {
|
||||
const QString expr = item->sourceExpression();
|
||||
if (!expr.isEmpty())
|
||||
values[expr] = item->value;
|
||||
});
|
||||
}
|
||||
Internal::setValueAnnotations(m_model->m_location, values);
|
||||
|
||||
m_model->m_contentsValid = true;
|
||||
updateLocalsWindow();
|
||||
m_model->reexpandItems();
|
||||
@@ -2750,6 +2767,11 @@ void WatchHandler::recordTypeInfo(const GdbMi &typeInfo)
|
||||
}
|
||||
}
|
||||
|
||||
void WatchHandler::setLocation(const Location &loc)
|
||||
{
|
||||
m_model->m_location = loc;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// WatchDelegate
|
||||
|
@@ -119,6 +119,8 @@ public:
|
||||
void reexpandItems();
|
||||
void recordTypeInfo(const GdbMi &typeInfo);
|
||||
|
||||
void setLocation(const Location &loc);
|
||||
|
||||
private:
|
||||
DebuggerEngine * const m_engine; // Not owned
|
||||
WatchModel *m_model; // Owned.
|
||||
|
Reference in New Issue
Block a user