diff options
author | hjk <[email protected]> | 2021-01-18 16:30:38 +0100 |
---|---|---|
committer | hjk <[email protected]> | 2021-01-20 09:45:03 +0000 |
commit | 79ade10c4afa5cf8db864f0ce4d446afa9e8f055 (patch) | |
tree | 0a76a17848674f04facdcc165938d85fd0b4f3aa /src/plugins/debugger | |
parent | 0ed99a954b0395c9f94264575fc1756b055fe6b4 (diff) |
Debugger: Add an option to show simple values as text annotations
Change-Id: I726d8559d7e28abd776ce483d5f670be5af09412
Reviewed-by: André Hartmann <[email protected]>
Reviewed-by: David Schulz <[email protected]>
Diffstat (limited to 'src/plugins/debugger')
-rw-r--r-- | src/plugins/debugger/commonoptionspage.cpp | 20 | ||||
-rw-r--r-- | src/plugins/debugger/debuggeractions.cpp | 9 | ||||
-rw-r--r-- | src/plugins/debugger/debuggeractions.h | 1 | ||||
-rw-r--r-- | src/plugins/debugger/debuggerengine.cpp | 1 | ||||
-rw-r--r-- | src/plugins/debugger/debuggerinternalconstants.h | 1 | ||||
-rw-r--r-- | src/plugins/debugger/sourceutils.cpp | 106 | ||||
-rw-r--r-- | src/plugins/debugger/sourceutils.h | 4 | ||||
-rw-r--r-- | src/plugins/debugger/watchdata.cpp | 26 | ||||
-rw-r--r-- | src/plugins/debugger/watchdata.h | 1 | ||||
-rw-r--r-- | src/plugins/debugger/watchhandler.cpp | 22 | ||||
-rw-r--r-- | src/plugins/debugger/watchhandler.h | 2 |
11 files changed, 181 insertions, 12 deletions
diff --git a/src/plugins/debugger/commonoptionspage.cpp b/src/plugins/debugger/commonoptionspage.cpp index bb17ee59962..0169fca2c1b 100644 --- a/src/plugins/debugger/commonoptionspage.cpp +++ b/src/plugins/debugger/commonoptionspage.cpp @@ -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), diff --git a/src/plugins/debugger/debuggeractions.cpp b/src/plugins/debugger/debuggeractions.cpp index 631bb881546..53e04570e7c 100644 --- a/src/plugins/debugger/debuggeractions.cpp +++ b/src/plugins/debugger/debuggeractions.cpp @@ -498,6 +498,15 @@ DebuggerSettings::DebuggerSettings() 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")); item->setToolTip(tr("<p>Checking this will enable tooltips for variable " diff --git a/src/plugins/debugger/debuggeractions.h b/src/plugins/debugger/debuggeractions.h index 30dbe7b9af9..8137f4c4008 100644 --- a/src/plugins/debugger/debuggeractions.h +++ b/src/plugins/debugger/debuggeractions.h @@ -98,6 +98,7 @@ enum DebuggerActionCode ShowThreadNames, UseToolTipsInMainEditor, + UseAnnotationsInMainEditor, UseToolTipsInLocalsView, UseToolTipsInBreakpointsView, UseToolTipsInStackView, diff --git a/src/plugins/debugger/debuggerengine.cpp b/src/plugins/debugger/debuggerengine.cpp index c9cfd774cf4..70e49c35e23 100644 --- a/src/plugins/debugger/debuggerengine.cpp +++ b/src/plugins/debugger/debuggerengine.cpp @@ -1113,6 +1113,7 @@ void DebuggerEngine::gotoLocation(const Location &loc) } d->m_breakHandler.setLocation(loc); + d->m_watchHandler.setLocation(loc); } void DebuggerEngine::gotoCurrentLocation() diff --git a/src/plugins/debugger/debuggerinternalconstants.h b/src/plugins/debugger/debuggerinternalconstants.h index 7137a9100c2..908a02c5d86 100644 --- a/src/plugins/debugger/debuggerinternalconstants.h +++ b/src/plugins/debugger/debuggerinternalconstants.h @@ -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"; diff --git a/src/plugins/debugger/sourceutils.cpp b/src/plugins/debugger/sourceutils.cpp index a76317258c6..fbb57679d6f 100644 --- a/src/plugins/debugger/sourceutils.cpp +++ b/src/plugins/debugger/sourceutils.cpp @@ -31,15 +31,20 @@ #include "watchdata.h" #include "watchutils.h" -#include <texteditor/texteditor.h> -#include <texteditor/textdocument.h> -#include <cpptools/abstracteditorsupport.h> -#include <cpptools/cppprojectfile.h> -#include <cpptools/cppmodelmanager.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 <texteditor/texteditor.h> +#include <texteditor/textdocument.h> + #include <utils/qtcassert.h> #include <QDebug> @@ -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 diff --git a/src/plugins/debugger/sourceutils.h b/src/plugins/debugger/sourceutils.h index a64c73295dc..634fa662037 100644 --- a/src/plugins/debugger/sourceutils.h +++ b/src/plugins/debugger/sourceutils.h @@ -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 diff --git a/src/plugins/debugger/watchdata.cpp b/src/plugins/debugger/watchdata.cpp index b8bcb522e01..26afbc75705 100644 --- a/src/plugins/debugger/watchdata.cpp +++ b/src/plugins/debugger/watchdata.cpp @@ -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 diff --git a/src/plugins/debugger/watchdata.h b/src/plugins/debugger/watchdata.h index 15146d9616e..0169d73bea2 100644 --- a/src/plugins/debugger/watchdata.h +++ b/src/plugins/debugger/watchdata.h @@ -51,6 +51,7 @@ public: bool isInspect() const; QString expression() const; + QString sourceExpression() const; QString realName() const; QString internalName() const; QString toToolTip() const; diff --git a/src/plugins/debugger/watchhandler.cpp b/src/plugins/debugger/watchhandler.cpp index 3bf6444b8ef..0068157296f 100644 --- a/src/plugins/debugger/watchhandler.cpp +++ b/src/plugins/debugger/watchhandler.cpp @@ -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 diff --git a/src/plugins/debugger/watchhandler.h b/src/plugins/debugger/watchhandler.h index 6d94249eea1..a0591bb5216 100644 --- a/src/plugins/debugger/watchhandler.h +++ b/src/plugins/debugger/watchhandler.h @@ -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. |