File indexing completed on 2024-05-12 04:37:37

0001 /*
0002     SPDX-FileCopyrightText: 2007 Hamish Rodda <rodda@kde.org>
0003     SPDX-FileCopyrightText: 2008 Vladimir Prus <ghost@cs.msu.su>
0004     SPDX-FileCopyrightText: 2009 Niko Sams <niko.sams@gmail.com>
0005 
0006     SPDX-License-Identifier: GPL-2.0-or-later
0007 */
0008 
0009 #include "variablecollection.h"
0010 
0011 #include <QFont>
0012 #include <QApplication>
0013 
0014 #include <KColorScheme>
0015 #include <KLocalizedString>
0016 #include <KTextEditor/TextHintInterface>
0017 #include <KTextEditor/Document>
0018 #include <KTextEditor/View>
0019 
0020 #include "../../interfaces/icore.h"
0021 #include "../../interfaces/idocumentcontroller.h"
0022 #include "../../interfaces/iuicontroller.h"
0023 #include "../../sublime/controller.h"
0024 #include "../../sublime/view.h"
0025 #include "../../interfaces/idebugcontroller.h"
0026 #include "../interfaces/idebugsession.h"
0027 #include "../interfaces/ivariablecontroller.h"
0028 #include <debug.h>
0029 #include "util/texteditorhelpers.h"
0030 #include "variabletooltip.h"
0031 #include <sublime/area.h>
0032 
0033 namespace KDevelop {
0034 
0035 IDebugSession* currentSession()
0036 {
0037     return ICore::self()->debugController()->currentSession();
0038 }
0039 
0040 IDebugSession::DebuggerState currentSessionState()
0041 {
0042     if (!currentSession()) return IDebugSession::NotStartedState;
0043     return currentSession()->state();
0044 }
0045 
0046 bool hasStartedSession()
0047 {
0048     IDebugSession::DebuggerState s = currentSessionState();
0049     return s != IDebugSession::NotStartedState && s != IDebugSession::EndedState;
0050 }
0051 
0052 Variable::Variable(TreeModel* model, TreeItem* parent,
0053                    const QString& expression,
0054                    const QString& display)
0055   : TreeItem(model, parent)
0056   , m_expression(expression)
0057   , m_inScope(true)
0058   , m_topLevel(true)
0059   , m_changed(false)
0060   , m_showError(false)
0061   , m_format(Natural)
0062 {
0063     // FIXME: should not duplicate the data, instead overload 'data'
0064     // and return expression_ directly.
0065     if (display.isEmpty())
0066         setData(QVector<QVariant>{expression, QString(), QString()});
0067     else
0068         setData(QVector<QVariant>{display, QString(), QString()});
0069 }
0070 
0071 QString Variable::expression() const
0072 {
0073     return m_expression;
0074 }
0075 
0076 bool Variable::inScope() const
0077 {
0078     return m_inScope;
0079 }
0080 
0081 void Variable::setValue(const QString& v)
0082 {
0083     itemData[VariableCollection::ValueColumn] = v;
0084     reportChange();
0085 }
0086 
0087 QString Variable::value() const
0088 {
0089     return itemData[VariableCollection::ValueColumn].toString();
0090 }
0091 
0092 void Variable::setType(const QString& type)
0093 {
0094     itemData[VariableCollection::TypeColumn] = type;
0095     reportChange();
0096 }
0097 
0098 QString Variable::type() const
0099 {
0100     return itemData[VariableCollection::TypeColumn].toString();
0101 }
0102 
0103 void Variable::setTopLevel(bool v)
0104 {
0105     m_topLevel = v;
0106 }
0107 
0108 void Variable::setInScope(bool v)
0109 {
0110     m_inScope = v;
0111     for (int i=0; i < childCount(); ++i) {
0112         if (auto *var = qobject_cast<Variable*>(child(i))) {
0113             var->setInScope(v);
0114         }
0115     }
0116     reportChange();
0117 }
0118 
0119 void Variable::setShowError (bool v)
0120 {
0121     m_showError = v;
0122     reportChange();
0123 }
0124 
0125 bool Variable::showError()
0126 {
0127     return m_showError;
0128 }
0129 
0130 
0131 Variable::~Variable()
0132 {
0133 }
0134 
0135 void Variable::die()
0136 {
0137     removeSelf();
0138     deleteLater();
0139 }
0140 
0141 
0142 void Variable::setChanged(bool c)
0143 {
0144     m_changed=c;
0145     reportChange();
0146 }
0147 
0148 void Variable::resetChanged()
0149 {
0150     setChanged(false);
0151     for (int i=0; i<childCount(); ++i) {
0152         TreeItem* childItem = child(i);
0153         if (qobject_cast<Variable*>(childItem)) {
0154             static_cast<Variable*>(childItem)->resetChanged();
0155         }
0156     }
0157 }
0158 
0159 Variable::format_t Variable::str2format(const QString& str)
0160 {
0161     if(str==QLatin1String("Binary") || str==QLatin1String("binary"))          return Binary;
0162     if(str==QLatin1String("Octal") || str==QLatin1String("octal"))            return Octal;
0163     if(str==QLatin1String("Decimal") || str==QLatin1String("decimal"))        return Decimal;
0164     if(str==QLatin1String("Hexadecimal") || str==QLatin1String("hexadecimal"))return Hexadecimal;
0165 
0166     return Natural; // maybe most reasonable default
0167 }
0168 
0169 QString Variable::format2str(format_t format)
0170 {
0171     switch(format) {
0172         case Natural:       return QStringLiteral("natural");
0173         case Binary:        return QStringLiteral("binary");
0174         case Octal:         return QStringLiteral("octal");
0175         case Decimal:       return QStringLiteral("decimal");
0176         case Hexadecimal:   return QStringLiteral("hexadecimal");
0177     }
0178     return QString();
0179 }
0180 
0181 
0182 void Variable::setFormat(Variable::format_t format)
0183 {
0184     if (m_format != format) {
0185         m_format = format;
0186         formatChanged();
0187     }
0188 }
0189 
0190 void Variable::formatChanged()
0191 {
0192 }
0193 
0194 bool Variable::isPotentialProblematicValue() const
0195 {
0196     const auto value = data(VariableCollection::ValueColumn, Qt::DisplayRole).toString();
0197     return value == QLatin1String("0x0");
0198 }
0199 
0200 QVariant Variable::data(int column, int role) const
0201 {
0202     if (m_showError) {
0203         if (role == Qt::FontRole) {
0204             QVariant ret = TreeItem::data(column, role);
0205             QFont font = ret.value<QFont>();
0206             font.setStyle(QFont::StyleItalic);
0207             return font;
0208         } else if (column == 1 && role == Qt::DisplayRole) {
0209             return i18n("Error");
0210         }
0211     }
0212     if (column == 1 && role == Qt::ForegroundRole)
0213     {
0214         KColorScheme scheme(QPalette::Active);
0215         if (!m_inScope) {
0216             return scheme.foreground(KColorScheme::InactiveText).color();
0217         } else if (isPotentialProblematicValue()) {
0218             return scheme.foreground(KColorScheme::NegativeText).color();
0219         } else if (m_changed) {
0220             return scheme.foreground(KColorScheme::NeutralText).color();
0221         }
0222     }
0223     if (role == Qt::ToolTipRole) {
0224         return TreeItem::data(column, Qt::DisplayRole);
0225     }
0226 
0227     return TreeItem::data(column, role);
0228 }
0229 
0230 Watches::Watches(TreeModel* model, TreeItem* parent)
0231 : TreeItem(model, parent), finishResult_(nullptr)
0232 {
0233     setData(QVector<QVariant>{i18n("Auto"), QString()});
0234 }
0235 
0236 Variable* Watches::add(const QString& expression)
0237 {
0238     if (!hasStartedSession()) return nullptr;
0239 
0240     Variable* v = currentSession()->variableController()->createVariable(
0241         model(), this, expression);
0242     appendChild(v);
0243     v->attachMaybe();
0244     if (childCount() == 1 && !isExpanded()) {
0245         setExpanded(true);
0246     }
0247     return v;
0248 }
0249 
0250 Variable *Watches::addFinishResult(const QString& convenienceVarible)
0251 {
0252     if( finishResult_ )
0253     {
0254         removeFinishResult();
0255     }
0256     finishResult_ = currentSession()->variableController()->createVariable(
0257         model(), this, convenienceVarible, QStringLiteral("$ret"));
0258     appendChild(finishResult_);
0259     finishResult_->attachMaybe();
0260     if (childCount() == 1 && !isExpanded()) {
0261         setExpanded(true);
0262     }
0263     return finishResult_;
0264 }
0265 
0266 void Watches::removeFinishResult()
0267 {
0268     if (finishResult_)
0269     {
0270         finishResult_->die();
0271         finishResult_ = nullptr;
0272     }
0273 }
0274 
0275 void Watches::resetChanged()
0276 {
0277     for (int i=0; i<childCount(); ++i) {
0278         TreeItem* childItem = child(i);
0279         if (qobject_cast<Variable*>(childItem)) {
0280             static_cast<Variable*>(childItem)->resetChanged();
0281         }
0282     }
0283 }
0284 
0285 QVariant Watches::data(int column, int role) const
0286 {
0287 #if 0
0288     if (column == 0 && role == Qt::FontRole)
0289     {
0290         /* FIXME: is creating font again and again efficient? */
0291         QFont f = font();
0292         f.setBold(true);
0293         return f;
0294     }
0295 #endif
0296     return TreeItem::data(column, role);
0297 }
0298 
0299 void Watches::reinstall()
0300 {
0301     for (int i = 0; i < childItems.size(); ++i)
0302     {
0303         auto* v = static_cast<Variable*>(child(i));
0304         v->attachMaybe();
0305     }
0306 }
0307 
0308 Locals::Locals(TreeModel* model, TreeItem* parent, const QString &name)
0309 : TreeItem(model, parent)
0310 {
0311     setData(QVector<QVariant>{name, QString()});
0312 }
0313 
0314 QList<Variable*> Locals::updateLocals(const QStringList& locals)
0315 {
0316     QSet<QString> existing, current;
0317     for (int i = 0; i < childItems.size(); i++)
0318     {
0319         Q_ASSERT(qobject_cast<KDevelop::Variable*>(child(i)));
0320         auto* var= static_cast<KDevelop::Variable*>(child(i));
0321         existing << var->expression();
0322     }
0323 
0324     for (const QString& var : locals) {
0325         current << var;
0326         // If we currently don't display this local var, add it.
0327         if( !existing.contains( var ) ) {
0328             // FIXME: passing variableCollection this way is awkward.
0329             // In future, variableCollection probably should get a
0330             // method to create variable.
0331             Variable* v =
0332                 currentSession()->variableController()->createVariable(
0333                     ICore::self()->debugController()->variableCollection(),
0334                     this, var );
0335             appendChild( v, false );
0336         }
0337     }
0338 
0339     for (int i = 0; i < childItems.size(); ++i) {
0340         auto* v = static_cast<KDevelop::Variable*>(child(i));
0341         if (!current.contains(v->expression())) {
0342             removeChild(i);
0343             --i;
0344             // FIXME: check that -var-delete is sent.
0345             delete v;
0346         }
0347     }
0348 
0349 
0350     if (hasMore()) {
0351         setHasMore(false);
0352     }
0353 
0354     // Variables which changed just value are updated by a call to -var-update.
0355     // Variables that changed type -- likewise.
0356 
0357     QList<Variable*> ret;
0358     ret.reserve(childItems.size());
0359     for (TreeItem* i : qAsConst(childItems)) {
0360         Q_ASSERT(qobject_cast<Variable*>(i));
0361         ret << static_cast<Variable*>(i);
0362     }
0363     return ret;
0364 }
0365 
0366 void Locals::resetChanged()
0367 {
0368     for (int i=0; i<childCount(); ++i) {
0369         TreeItem* childItem = child(i);
0370         if (qobject_cast<Variable*>(childItem)) {
0371             static_cast<Variable*>(childItem)->resetChanged();
0372         }
0373     }
0374 }
0375 
0376 VariablesRoot::VariablesRoot(TreeModel* model)
0377     : TreeItem(model)
0378     , m_watches(new Watches(model, this))
0379 {
0380     appendChild(m_watches, true);
0381 }
0382 
0383 
0384 Locals* VariablesRoot::locals(const QString& name)
0385 {
0386     auto localsIt = m_locals.find(name);
0387     if (localsIt == m_locals.end()) {
0388         localsIt = m_locals.insert(name, new Locals(model(), this, name));
0389         appendChild(*localsIt);
0390     }
0391     return *localsIt;
0392 }
0393 
0394 QHash<QString, Locals*> VariablesRoot::allLocals() const
0395 {
0396     return m_locals;
0397 }
0398 
0399 void VariablesRoot::resetChanged()
0400 {
0401     m_watches->resetChanged();
0402     for (Locals* l : qAsConst(m_locals)) {
0403         l->resetChanged();
0404     }
0405 }
0406 
0407 VariableCollection::VariableCollection(IDebugController* controller)
0408     : TreeModel({i18n("Name"), i18n("Value"), i18n("Type")}, controller)
0409     , m_widgetVisible(false)
0410     , m_textHintProvider(this)
0411 {
0412     m_universe = new VariablesRoot(this);
0413     setRootItem(m_universe);
0414 
0415     //new ModelTest(this);
0416 
0417     connect (ICore::self()->documentController(),
0418          &IDocumentController::textDocumentCreated,
0419          this,
0420          &VariableCollection::textDocumentCreated );
0421 
0422     connect(controller, &IDebugController::currentSessionChanged,
0423              this, &VariableCollection::updateAutoUpdate);
0424 
0425     // Qt5 signal slot syntax does not support default arguments
0426     auto callUpdateAutoUpdate = [&] { updateAutoUpdate(); };
0427 
0428     connect(locals(), &Locals::expanded, this, callUpdateAutoUpdate);
0429     connect(locals(), &Locals::collapsed, this, callUpdateAutoUpdate);
0430     connect(watches(), &Watches::expanded, this, callUpdateAutoUpdate);
0431     connect(watches(), &Watches::collapsed, this, callUpdateAutoUpdate);
0432 }
0433 
0434 void VariableCollection::variableWidgetHidden()
0435 {
0436     m_widgetVisible = false;
0437     updateAutoUpdate();
0438 }
0439 
0440 void VariableCollection::variableWidgetShown()
0441 {
0442     m_widgetVisible = true;
0443     updateAutoUpdate();
0444 }
0445 
0446 void VariableCollection::updateAutoUpdate(IDebugSession* session)
0447 {
0448     if (!session) session = currentSession();
0449     qCDebug(DEBUGGER) << session;
0450     if (!session) return;
0451 
0452     if (!m_widgetVisible) {
0453         session->variableController()->setAutoUpdate(IVariableController::UpdateNone);
0454     } else {
0455         QFlags<IVariableController::UpdateType> t = IVariableController::UpdateNone;
0456         if (locals()->isExpanded()) t |= IVariableController::UpdateLocals;
0457         if (watches()->isExpanded()) t |= IVariableController::UpdateWatches;
0458         session->variableController()->setAutoUpdate(t);
0459     }
0460 }
0461 
0462 VariableCollection::~ VariableCollection()
0463 {
0464     for (auto* view : qAsConst(m_textHintProvidedViews)) {
0465         auto* iface = qobject_cast<KTextEditor::TextHintInterface*>(view);
0466         iface->unregisterTextHintProvider(&m_textHintProvider);
0467     }
0468 }
0469 
0470 void VariableCollection::textDocumentCreated(IDocument* doc)
0471 {
0472   connect( doc->textDocument(),
0473        &KTextEditor::Document::viewCreated,
0474        this, &VariableCollection::viewCreated );
0475 
0476   const auto views = doc->textDocument()->views();
0477   for (KTextEditor::View* view : views) {
0478     viewCreated( doc->textDocument(), view );
0479   }
0480 }
0481 
0482 void VariableCollection::viewCreated(KTextEditor::Document* doc,
0483                                      KTextEditor::View* view)
0484 {
0485     Q_UNUSED(doc);
0486     using namespace KTextEditor;
0487     auto* iface = qobject_cast<TextHintInterface*>(view);
0488     if( !iface )
0489         return;
0490 
0491     if (m_textHintProvidedViews.contains(view)) {
0492         return;
0493     }
0494     connect(view, &View::destroyed, this, [this, view](QObject* obj) {
0495         Q_ASSERT(obj == view);
0496         m_textHintProvidedViews.removeOne(view);
0497     });
0498 
0499     iface->registerTextHintProvider(&m_textHintProvider);
0500     m_textHintProvidedViews.append(view);
0501 }
0502 
0503 Locals* VariableCollection::locals(const QString &name) const
0504 {
0505     return m_universe->locals(name.isEmpty() ? i18n("Locals") : name);
0506 }
0507 
0508 VariableProvider::VariableProvider(VariableCollection* collection)
0509     : KTextEditor::TextHintProvider()
0510     , m_collection(collection)
0511 {
0512 }
0513 
0514 QString VariableProvider::textHint(KTextEditor::View* view, const KTextEditor::Cursor& cursor)
0515 {
0516     if (!hasStartedSession())
0517         return QString();
0518 
0519     if (ICore::self()->uiController()->activeArea()->objectName() != QLatin1String("debug"))
0520         return QString();
0521 
0522     //TODO: These keyboardModifiers should also hide already opened tooltip, and show another one for code area.
0523     if (QApplication::keyboardModifiers() == Qt::ControlModifier ||
0524         QApplication::keyboardModifiers() == Qt::AltModifier){
0525         return QString();
0526     }
0527 
0528     KTextEditor::Document* doc = view->document();
0529 
0530     KTextEditor::Range expressionRange = currentSession()->variableController()->expressionRangeUnderCursor(doc, cursor);
0531 
0532     if (!expressionRange.isValid())
0533         return QString();
0534     QString expression = doc->text(expressionRange).trimmed();
0535 
0536     // Don't do anything if there's already an open tooltip with matching range
0537     if (m_collection->m_activeTooltip && m_collection->m_activeTooltip->variable()->expression() == expression)
0538         return QString();
0539     if (expression.isEmpty())
0540         return QString();
0541 
0542     QPoint local = view->cursorToCoordinate(cursor);
0543     QPoint global = view->mapToGlobal(local);
0544     QWidget* w = view->childAt(local);
0545     if (!w)
0546         w = view;
0547 
0548     m_collection->m_activeTooltip = new VariableToolTip(w, global+QPoint(30,30), expression);
0549     m_collection->m_activeTooltip->setHandleRect(KTextEditorHelpers::itemBoundingRect(view, expressionRange));
0550     return QString();
0551 }
0552 
0553 }
0554 
0555 #include "moc_variablecollection.cpp"