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:
hjk
2021-01-18 16:30:38 +01:00
parent 0ed99a954b
commit 79ade10c4a
11 changed files with 181 additions and 12 deletions

View File

@@ -83,6 +83,9 @@ public:
auto checkBoxUseToolTipsInMainEditor = new QCheckBox(behaviorBox); auto checkBoxUseToolTipsInMainEditor = new QCheckBox(behaviorBox);
checkBoxUseToolTipsInMainEditor->setText(tr("Use tooltips in main editor while debugging")); 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 " QString t = tr("Stopping and stepping in the debugger "
"will automatically open views associated with the current location.") + '\n'; "will automatically open views associated with the current location.") + '\n';
auto checkBoxCloseSourceBuffersOnExit = new QCheckBox(behaviorBox); auto checkBoxCloseSourceBuffersOnExit = new QCheckBox(behaviorBox);
@@ -146,13 +149,14 @@ public:
auto gridLayout = new QGridLayout(behaviorBox); auto gridLayout = new QGridLayout(behaviorBox);
gridLayout->addWidget(checkBoxUseAlternatingRowColors, 0, 0, 1, 1); gridLayout->addWidget(checkBoxUseAlternatingRowColors, 0, 0, 1, 1);
gridLayout->addWidget(checkBoxUseToolTipsInMainEditor, 1, 0, 1, 1); gridLayout->addWidget(checkBoxUseAnnotationsInMainEditor, 1, 0, 1, 1);
gridLayout->addWidget(checkBoxCloseSourceBuffersOnExit, 2, 0, 1, 1); gridLayout->addWidget(checkBoxUseToolTipsInMainEditor, 2, 0, 1, 1);
gridLayout->addWidget(checkBoxCloseMemoryBuffersOnExit, 3, 0, 1, 1); gridLayout->addWidget(checkBoxCloseSourceBuffersOnExit, 3, 0, 1, 1);
gridLayout->addWidget(checkBoxBringToForegroundOnInterrrupt, 4, 0, 1, 1); gridLayout->addWidget(checkBoxCloseMemoryBuffersOnExit, 4, 0, 1, 1);
gridLayout->addWidget(checkBoxBreakpointsFullPath, 5, 0, 1, 1); gridLayout->addWidget(checkBoxBringToForegroundOnInterrrupt, 5, 0, 1, 1);
gridLayout->addWidget(checkBoxWarnOnReleaseBuilds, 6, 0, 1, 1); gridLayout->addWidget(checkBoxBreakpointsFullPath, 6, 0, 1, 1);
gridLayout->addLayout(horizontalLayout, 7, 0, 1, 2); gridLayout->addWidget(checkBoxWarnOnReleaseBuilds, 7, 0, 1, 1);
gridLayout->addLayout(horizontalLayout, 8, 0, 1, 2);
gridLayout->addWidget(checkBoxFontSizeFollowsEditor, 0, 1, 1, 1); gridLayout->addWidget(checkBoxFontSizeFollowsEditor, 0, 1, 1, 1);
gridLayout->addWidget(checkBoxSwitchModeOnExit, 1, 1, 1, 1); gridLayout->addWidget(checkBoxSwitchModeOnExit, 1, 1, 1, 1);
@@ -167,6 +171,8 @@ public:
m_group.insert(action(UseAlternatingRowColors), m_group.insert(action(UseAlternatingRowColors),
checkBoxUseAlternatingRowColors); checkBoxUseAlternatingRowColors);
m_group.insert(action(UseAnnotationsInMainEditor),
checkBoxUseAnnotationsInMainEditor);
m_group.insert(action(UseToolTipsInMainEditor), m_group.insert(action(UseToolTipsInMainEditor),
checkBoxUseToolTipsInMainEditor); checkBoxUseToolTipsInMainEditor);
m_group.insert(action(CloseSourceBuffersOnExit), m_group.insert(action(CloseSourceBuffersOnExit),

View File

@@ -497,6 +497,15 @@ DebuggerSettings::DebuggerSettings()
item->setDefaultValue(false); item->setDefaultValue(false);
insertItem(IntelFlavor, item); 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 = new SavedAction;
item->setSettingsKey(debugModeGroup, "UseToolTips"); item->setSettingsKey(debugModeGroup, "UseToolTips");
item->setText(tr("Use tooltips in main editor when debugging")); item->setText(tr("Use tooltips in main editor when debugging"));

View File

@@ -98,6 +98,7 @@ enum DebuggerActionCode
ShowThreadNames, ShowThreadNames,
UseToolTipsInMainEditor, UseToolTipsInMainEditor,
UseAnnotationsInMainEditor,
UseToolTipsInLocalsView, UseToolTipsInLocalsView,
UseToolTipsInBreakpointsView, UseToolTipsInBreakpointsView,
UseToolTipsInStackView, UseToolTipsInStackView,

View File

@@ -1113,6 +1113,7 @@ void DebuggerEngine::gotoLocation(const Location &loc)
} }
d->m_breakHandler.setLocation(loc); d->m_breakHandler.setLocation(loc);
d->m_watchHandler.setLocation(loc);
} }
void DebuggerEngine::gotoCurrentLocation() void DebuggerEngine::gotoCurrentLocation()

View File

@@ -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_BREAKPOINT[] = "Debugger.Mark.Breakpoint";
const char TEXT_MARK_CATEGORY_LOCATION[] = "Debugger.Mark.Location"; 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_BY_DEBUGGER[] = "OpenedByDebugger";
const char OPENED_WITH_DISASSEMBLY[] = "DisassemblerView"; const char OPENED_WITH_DISASSEMBLY[] = "DisassemblerView";

View File

@@ -31,14 +31,19 @@
#include "watchdata.h" #include "watchdata.h"
#include "watchutils.h" #include "watchutils.h"
#include <texteditor/texteditor.h> #include <coreplugin/editormanager/ieditor.h>
#include <texteditor/textdocument.h>
#include <cplusplus/CppDocument.h>
#include <cplusplus/ExpressionUnderCursor.h>
#include <cplusplus/LookupItem.h>
#include <cplusplus/Overview.h>
#include <cpptools/abstracteditorsupport.h> #include <cpptools/abstracteditorsupport.h>
#include <cpptools/cppprojectfile.h> #include <cpptools/cppprojectfile.h>
#include <cpptools/cppmodelmanager.h> #include <cpptools/cppmodelmanager.h>
#include <cplusplus/CppDocument.h>
#include <cplusplus/ExpressionUnderCursor.h> #include <texteditor/texteditor.h>
#include <cplusplus/Overview.h> #include <texteditor/textdocument.h>
#include <utils/qtcassert.h> #include <utils/qtcassert.h>
@@ -54,6 +59,7 @@ enum { debug = 0 };
using namespace CppTools; using namespace CppTools;
using namespace CPlusPlus; using namespace CPlusPlus;
using namespace TextEditor; using namespace TextEditor;
using namespace Utils;
namespace CPlusPlus { namespace CPlusPlus {
@@ -343,5 +349,95 @@ ContextData getLocationContext(TextDocument *document, int lineNumber)
return data; 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 Internal
} // namespace Debugger } // namespace Debugger

View File

@@ -25,6 +25,7 @@
#pragma once #pragma once
#include <QMap>
#include <QString> #include <QString>
namespace TextEditor { namespace TextEditor {
@@ -38,6 +39,7 @@ namespace Debugger {
namespace Internal { namespace Internal {
class ContextData; class ContextData;
class Location;
// Editor tooltip support // Editor tooltip support
QString cppExpressionAt(TextEditor::TextEditorWidget *editorWidget, int pos, QString cppExpressionAt(TextEditor::TextEditorWidget *editorWidget, int pos,
@@ -54,5 +56,7 @@ QStringList getUninitializedVariables(const CPlusPlus::Snapshot &snapshot,
ContextData getLocationContext(TextEditor::TextDocument *document, int lineNumber); ContextData getLocationContext(TextEditor::TextDocument *document, int lineNumber);
void setValueAnnotations(const Location &loc, const QMap<QString, QString> &values);
} // namespace Internal } // namespace Internal
} // namespace Debugger } // namespace Debugger

View File

@@ -520,6 +520,7 @@ QString WatchItem::toToolTip() const
formatToolTipRow(str, tr("Static Object Size"), tr("%n bytes", nullptr, size)); formatToolTipRow(str, tr("Static Object Size"), tr("%n bytes", nullptr, size));
formatToolTipRow(str, tr("Internal ID"), internalName()); formatToolTipRow(str, tr("Internal ID"), internalName());
formatToolTipRow(str, tr("Creation Time in ms"), QString::number(int(time * 1000))); formatToolTipRow(str, tr("Creation Time in ms"), QString::number(int(time * 1000)));
formatToolTipRow(str, tr("Source"), sourceExpression());
str << "</table></body></html>"; str << "</table></body></html>";
return res; return res;
} }
@@ -578,6 +579,31 @@ QString WatchItem::expression() const
return name; 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 Internal
} // namespace Debugger } // namespace Debugger

View File

@@ -51,6 +51,7 @@ public:
bool isInspect() const; bool isInspect() const;
QString expression() const; QString expression() const;
QString sourceExpression() const;
QString realName() const; QString realName() const;
QString internalName() const; QString internalName() const;
QString toToolTip() const; QString toToolTip() const;

View File

@@ -38,6 +38,7 @@
#include "memoryagent.h" #include "memoryagent.h"
#include "registerhandler.h" #include "registerhandler.h"
#include "simplifytype.h" #include "simplifytype.h"
#include "sourceutils.h"
#include "watchdelegatewidgets.h" #include "watchdelegatewidgets.h"
#include "watchutils.h" #include "watchutils.h"
@@ -67,6 +68,7 @@
#include <QJsonArray> #include <QJsonArray>
#include <QJsonObject> #include <QJsonObject>
#include <QLabel> #include <QLabel>
#include <QMap>
#include <QMenu> #include <QMenu>
#include <QMimeData> #include <QMimeData>
#include <QPainter> #include <QPainter>
@@ -473,6 +475,8 @@ public:
QHash<QString, TypeInfo> m_reportedTypeInfo; QHash<QString, TypeInfo> m_reportedTypeInfo;
QHash<QString, DisplayFormats> m_reportedTypeFormats; // Type name -> Dumper Formats QHash<QString, DisplayFormats> m_reportedTypeFormats; // Type name -> Dumper Formats
QHash<QString, QString> m_valueCache; QHash<QString, QString> m_valueCache;
Location m_location;
}; };
WatchModel::WatchModel(WatchHandler *handler, DebuggerEngine *engine) WatchModel::WatchModel(WatchHandler *handler, DebuggerEngine *engine)
@@ -518,6 +522,8 @@ WatchModel::WatchModel(WatchHandler *handler, DebuggerEngine *engine)
m_engine, &DebuggerEngine::updateAll); m_engine, &DebuggerEngine::updateAll);
connect(action(ShowQObjectNames), &SavedAction::valueChanged, connect(action(ShowQObjectNames), &SavedAction::valueChanged,
m_engine, &DebuggerEngine::updateAll); m_engine, &DebuggerEngine::updateAll);
connect(action(UseAnnotationsInMainEditor), &SavedAction::valueChanged,
m_engine, &DebuggerEngine::updateAll);
connect(SessionManager::instance(), &SessionManager::sessionLoaded, connect(SessionManager::instance(), &SessionManager::sessionLoaded,
this, &loadSessionData); this, &loadSessionData);
@@ -2081,6 +2087,7 @@ void WatchHandler::cleanup()
theTemporaryWatchers.clear(); theTemporaryWatchers.clear();
saveWatchers(); saveWatchers();
m_model->reinitialize(); m_model->reinitialize();
Internal::setValueAnnotations(m_model->m_location, {});
emit m_model->updateFinished(); emit m_model->updateFinished();
m_model->m_separatedView->hide(); 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; m_model->m_contentsValid = true;
updateLocalsWindow(); updateLocalsWindow();
m_model->reexpandItems(); 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 // WatchDelegate

View File

@@ -119,6 +119,8 @@ public:
void reexpandItems(); void reexpandItems();
void recordTypeInfo(const GdbMi &typeInfo); void recordTypeInfo(const GdbMi &typeInfo);
void setLocation(const Location &loc);
private: private:
DebuggerEngine * const m_engine; // Not owned DebuggerEngine * const m_engine; // Not owned
WatchModel *m_model; // Owned. WatchModel *m_model; // Owned.